The {% schema %} tag is the contract between your Liquid markup and the Shopify theme editor. It tells Shopify what a merchant can configure, how blocks behave, and where a section is allowed to appear. Get it right and your section is flexible and editor-friendly; get it wrong and you'll see syntax errors or settings that silently do nothing.
This guide explains the section schema attributes used in the examples, the rules that govern the tag, and patterns that make a schema easier to maintain. Use the Schema Generator to assemble a draft, then validate it against Shopify's current documentation; keep the cheatsheet handy for the Liquid that reads those settings.
The rules of {% schema %}
Before the attributes, internalize these constraints — most "my section won't save" problems trace back to one of them:
- A section file may contain exactly one
{% schema %}tag. - The tag must contain valid JSON — no comments, no trailing commas, no Liquid.
- It cannot be nested inside another Liquid tag.
- It doesn't output anything or run any Liquid inside it.
- It can be placed anywhere in the file, but convention is the bottom.
Section attributes at a glance
A section schema supports the following top-level attributes:
| Attribute | Purpose |
|---|---|
name |
Title shown in the theme editor |
tag |
HTML element that wraps the section output |
class |
Extra CSS class on the wrapper |
limit |
Max times the section can be added to a template |
settings |
Array of merchant-configurable inputs |
blocks |
Repeatable, reorderable child components |
max_blocks |
Cap on the number of blocks (max 50) |
presets |
Makes the section available as a dynamic section |
default |
Default configuration for statically rendered sections |
locales |
Section-specific translation strings |
enabled_on |
Restrict where the section can appear |
disabled_on |
Prevent the section from appearing in specific places |
Let's go through each.
name
The human-readable title in the "Add section" menu and section list.
{
"name": "Hero Banner"
}
tag and class
By default Shopify wraps each section in a <div id="shopify-section-{{ section.id }}">. Use tag to change the wrapper element and class to add classes:
{
"name": "Hero Banner",
"tag": "section",
"class": "section-hero"
}
limit
Restricts how many instances of the section a merchant can add to a single template. Useful for sections that should be unique, like a main product section:
{
"name": "Header",
"limit": 1
}
settings
The array of inputs that appear in the editor. Each setting needs a type, most need an id and label, and many accept a default. There are basic inputs (text, textarea, richtext, inline_richtext, number, range, checkbox, radio, select, liquid) and specialized ones (image_picker, video, video_url, url, collection, collection_list, product, product_list, article, article_list, blog, page, link_list, font_picker, html, color, color_background, color_scheme, color_palette, text_alignment, metaobject, metaobject_list). There are also two sidebar types that carry no value: header and paragraph.
"settings": [
{ "type": "header", "content": "Content" },
{ "type": "text", "id": "heading", "label": "Heading", "default": "Welcome" },
{ "type": "richtext", "id": "body", "label": "Body" },
{
"type": "select",
"id": "layout",
"label": "Layout",
"options": [
{ "value": "grid", "label": "Grid" },
{ "value": "list", "label": "List" }
],
"default": "grid"
},
{
"type": "range",
"id": "padding",
"label": "Padding",
"min": 0, "max": 100, "step": 4, "unit": "px",
"default": 40
}
]
You access each value in Liquid with section.settings.<id>. For a full breakdown of every type and when to use it, see the Schema Generator.
Conditional settings with visible_if
Most settings support visible_if, which shows or hides a setting based on another value — perfect for progressive disclosure:
{
"type": "select", "id": "layout_style", "label": "Layout",
"options": [{ "value": "flex", "label": "Stack" }, { "value": "grid", "label": "Grid" }],
"default": "flex"
},
{
"type": "range", "id": "columns", "label": "Columns",
"min": 2, "max": 6, "step": 1, "default": 3,
"visible_if": "{{ section.settings.layout_style == 'grid' }}"
}
blocks and max_blocks
Blocks are repeatable components a merchant can add, remove, and reorder. Each block has its own type, name, and settings. max_blocks caps the count (the platform limit is 50):
"blocks": [
{
"type": "feature",
"name": "Feature",
"settings": [
{ "type": "text", "id": "title", "label": "Title" },
{ "type": "richtext", "id": "text", "label": "Text" }
]
}
],
"max_blocks": 6
In markup you loop section.blocks and must output {{ block.shopify_attributes }} on each block's wrapper so the editor can target it:
{% for block in section.blocks %}
<div {{ block.shopify_attributes }}>
{% case block.type %}
{% when 'feature' %}
<h3>{{ block.settings.title }}</h3>
<div>{{ block.settings.text }}</div>
{% endcase %}
</div>
{% endfor %}
To accept theme blocks (reusable blocks in the blocks/ folder) or app blocks, use the special types { "type": "@theme" } and { "type": "@app" }, and render them with {% content_for 'blocks' %}.
Dynamic block titles
The theme editor labels a block in the sidebar using the value of a setting whose id is, in order of preference, heading, then title, then text. If none exist, it falls back to the block's name. Naming a text setting heading gives merchants a readable block label for free.
presets
A preset makes your section appear in the theme editor's "Add section" menu (i.e. a dynamic section). Presets can pre-populate settings and blocks:
"presets": [
{
"name": "Hero Banner",
"settings": { "heading": "Summer Sale" },
"blocks": [{ "type": "feature" }, { "type": "feature" }]
}
]
A section with presets shouldn't be statically rendered. If you render a section with
{% section 'name' %}, configure it withdefaultinstead.
default
default has the same shape as a preset but applies to statically rendered sections — those included directly in a template or layout. Use it to ship a sensible starting configuration.
locales
Section-level translations, separate from the theme's locales/ directory. Handy for sections distributed across multiple themes or shops:
"locales": {
"en": { "title": "Slideshow" },
"fr": { "title": "Diaporama" }
}
enabled_on and disabled_on
Control where a section can be used by template and section group. Use only one of the two:
"enabled_on": {
"templates": ["index", "product"],
"groups": ["header", "footer"]
}
"disabled_on": {
"templates": ["password"]
}
A complete, annotated example
Putting it together:
{% schema %}
{
"name": "Hero Banner",
"tag": "section",
"class": "section-hero",
"limit": 2,
"settings": [
{ "type": "text", "id": "heading", "label": "Heading", "default": "Welcome" },
{ "type": "richtext", "id": "subheading", "label": "Subheading" },
{ "type": "color", "id": "text_color", "label": "Text color", "default": "#ffffff" }
],
"blocks": [
{
"type": "feature",
"name": "Feature",
"settings": [
{ "type": "text", "id": "title", "label": "Title" }
]
}
],
"max_blocks": 4,
"enabled_on": { "templates": ["index"] },
"presets": [
{ "name": "Hero Banner", "blocks": [{ "type": "feature" }] }
]
}
{% endschema %}
Common mistakes
- Trailing commas or comments in the JSON — both cause a save error.
- More than one
{% schema %}tag — only one is allowed per file. - Forgetting
{{ block.shopify_attributes }}— blocks become un-selectable in the editor. - Using both
enabled_onanddisabled_on— pick one. - Adding
presetsto a statically rendered section — usedefaultinstead. - Duplicate setting
ids — later values silently overwrite earlier ones.
Build it faster
You don't have to hand-write schema JSON. The Schema Generator builds valid settings and blocks for you, and the HTML to Liquid converter produces both the markup and a matching schema from a block of HTML. If you're new to the workflow, start with our guide on converting HTML into a Shopify section.
Conclusion
Confirm limits and newly introduced attributes in Shopify's official section schema documentation.
The section schema is small but precise: a dozen attributes, strict JSON, and a few firm rules. Once you know what each attribute does — and which patterns (presets vs default, enabled_on vs disabled_on, blocks vs settings) apply when — you can author sections that are powerful for developers and effortless for merchants.
Keep learning: explore every input setting type, reference the full Liquid cheatsheet, and try the free converter.