Building themes
Setting types
Every setting type you can put in a schema, its JSON options, and the value your template receives.
How settings work
Each entry in a section's "settings" list becomes a field in the visual editor's settings panel. The owner fills it in. Your template reads the value from section.settings:
{%- assign s = section.settings -%}
<h2>{{ s.heading }}</h2>Plangku checks every value before your template sees it, so a number setting always gives a number and a checkbox always gives true or false. A missing value becomes an empty text, 0, false, an empty list or an empty link.
The same setting types are used in config/settings_schema.json. See Theme settings for the differences.
Options every setting has
| Key | Meaning |
|---|---|
type | Required. One of the types below |
id | Required. Starts with a letter, then letters, numbers and _, up to 40 characters. Must be unique in the section. Reserved ids are not allowed |
label | The name in the settings panel, up to 80 characters. Defaults to the id |
default | The starting value. What is valid depends on the type |
info | A line of help text under the field, up to 300 characters |
placeholder | Grey hint text, up to 120 characters. Only text and textarea use it |
show_if | Show the field only when another field has a given value |
A new section starts with every default. A section already on a page has no stored value for a setting you add later, so it renders with that setting's default (and the settings panel shows the default too) until the owner changes it. A value the owner cleared stays empty; use the default filter when even an emptied setting needs a fallback: {{ s.heading | default: 'Selamat datang' }}.
The types
| Type | JSON options | The template receives |
|---|---|---|
text | placeholder | Text |
textarea, richtext | placeholder, rows (2 to 20, default 4) | Text, with line breaks kept |
number | min, max, step | A number. 0 when empty |
range | min, max (required), step, unit | A number inside min and max |
color | none | A hex colour like #E11D48, or empty text |
select, radio | options (required) | The chosen option's value as text |
checkbox, toggle | none | true or false |
image, image_picker | none | A safe image address, or empty text |
link, url | none | A link object (see below), or empty text |
icon | none | The icon's name as text |
collection | none | A collection with its items, or nothing. Sections only |
list | fields, item_label, max, add_label | A list of items |
richtext is the same as textarea. There is no rich text editor. radio is the same as select, toggle the same as checkbox, image_picker the same as image and url the same as link.
text
{ "type": "text", "id": "heading", "label": "Heading", "default": "Kenapa pilih kami", "placeholder": "Tulis judul" }<h2>{{ s.heading }}</h2>Output is HTML-escaped. Only default text of the right type is kept; anything else becomes an empty text.
textarea and richtext
{ "type": "textarea", "id": "text", "label": "Text", "rows": 3, "default": "Pesan sebelum jam 3 sore, sampai hari ini juga." }{{ s.text | paragraphs }}Use the paragraphs or lines filter to turn line breaks into markup, or markdown for bold and lists. See Filters.
number
{ "type": "number", "id": "columns", "label": "Columns", "min": 1, "max": 4, "step": 1, "default": 3 }<div class="grid grid-{{ s.columns }}">range
A slider. min and max are required, and max must be larger. step is 1 if you leave it out. A default outside the range is pulled inside it. With no default, the value is min.
{ "type": "range", "id": "image_height", "label": "Photo height", "min": 200, "max": 600, "step": 20, "unit": "px", "default": 360 }<img style="height: {{ s.image_height }}px" src="{{ s.photo }}" alt="">unit (up to 8 characters) is only a label in the panel. The template receives the plain number.
color
{ "type": "color", "id": "badge_color", "label": "Badge colour", "default": "#E11D48" }{% if s.badge_color != blank %}<span style="background: {{ s.badge_color }}">Promo</span>{% endif %}A default must be #RGB or #RRGGBB. Otherwise the default is empty.
select and radio
options is a list of text values, or of { "value", "label" } objects. You can have up to 50. The default must match one of the values, or the first option is used.
{
"type": "select",
"id": "layout",
"label": "Layout",
"default": "left",
"options": [
{ "value": "left", "label": "Photo on the left" },
{ "value": "right", "label": "Photo on the right" }
]
}<div class="layout-{{ s.layout }}">checkbox and toggle
{ "type": "checkbox", "id": "show_badge", "label": "Show the badge", "default": true }{% if s.show_badge %}<span class="badge">Terlaris</span>{% endif %}Only "default": true turns it on. Anything else is off.
image and image_picker
The owner picks or uploads a photo. A default can be an https:// address, an uploaded path, or assets/<file> for an image that came with a theme package.
{ "type": "image", "id": "photo", "label": "Photo" }{% render 'image', src: s.photo, alt: s.heading, class: 'aspect-[4/3] w-full rounded-site' %}The image platform snippet draws a placeholder when the photo is empty. Only https:// and /uploads/ addresses pass. Anything else becomes empty text.
link and url
The owner types or picks a destination. The stored value can be wa:Halo, saya mau pesan (WhatsApp with a message), page:menu (a page), #kontak (an anchor), tel:, mailto: or a web address.
{ "type": "link", "id": "button_link", "label": "Button link", "default": "wa:Halo, saya mau pesan" }The template gets a link object. Writing {{ s.button_link }} prints the address. You can also read:
| Property | Value |
|---|---|
.kind | whatsapp, page, anchor, phone, email or url |
.external | true for WhatsApp links and http(s) addresses |
.href | The address |
{% if s.button_link != blank %}
<a href="{{ s.button_link }}"{% if s.button_link.external %} target="_blank" rel="noopener noreferrer"{% endif %}>Pesan lewat WhatsApp</a>
{% endif %}The button platform snippet does this for you: {% render 'button', label: 'Pesan', link: s.button_link %}.
icon
{ "type": "icon", "id": "icon", "label": "Icon", "default": "ShieldCheck" }{{ s.icon | icon: class: 'size-6' }}The value is the icon's name, as text. The icon filter turns it into an inline SVG.
collection
Lets the owner choose one of the website's collections, such as products or menu items. It cannot be used inside a list or in theme settings. The template gets the collection (name, slug, url and items).
{ "type": "collection", "id": "products", "label": "Products to show" }{% for item in s.products.items %}
<h3>{{ item.title }}</h3>
<p>{{ item.price | money }}</p>
{% endfor %}See Collections and data for the fields on an item.
list
A repeatable group of fields, such as cards or FAQ rows. fields lists the fields of one item.
| Key | Meaning |
|---|---|
fields | Required. Up to 20 simple settings. A list cannot contain a list or a collection |
item_label | The id of the field whose value names each item in the panel. Defaults to the first text field |
max | The most items allowed, 1 to 60. Default 24 |
add_label | The "add" button text, up to 40 characters. Default "Add item" |
default | A list of items to start with |
{
"type": "list",
"id": "faqs",
"label": "Questions",
"item_label": "question",
"max": 12,
"add_label": "Add question",
"fields": [
{ "type": "text", "id": "question", "label": "Question", "default": "Apakah bisa kirim ke luar kota?" },
{ "type": "textarea", "id": "answer", "label": "Answer", "default": "" }
],
"default": [
{ "question": "Apakah bisa kirim ke luar kota?", "answer": "Bisa. Kami kirim lewat JNE dan J&T." },
{ "question": "Bagaimana cara bayar?", "answer": "Transfer bank atau QRIS." }
]
}{% for faq in s.faqs %}
<details>
<summary>{{ faq.question }}</summary>
<p>{{ faq.answer }}</p>
</details>
{% endfor %}Each item has the fields you defined, already checked by type. Items added in the editor start with each field's default. An item saved before you added a field has no value for it: the template gets empty text (or 0, false), never the sample default, so a placeholder price can't appear on a real item.
Showing a field only sometimes
show_if hides a field until another field has one of the listed values. The listed values are text or booleans.
{ "type": "select", "id": "layout", "label": "Layout", "default": "split", "options": ["split", "centered"] },
{ "type": "image", "id": "photo", "label": "Photo", "show_if": { "field": "layout", "is": ["split"] } },
{ "type": "checkbox", "id": "show_price", "label": "Show price", "default": true },
{ "type": "text", "id": "price_note", "label": "Price note", "show_if": { "field": "show_price", "is": [true] } }The hidden field's value is still stored and still reaches the template. Use {% if %} there as well when it matters.
Related
- Sections: where
settingssits in the schema. - Theme settings: the same types, for the whole website.
- Objects: everything else a template can read.