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 is a complete reference to every section schema attribute, the rules that govern the tag, and the patterns that separate a beginner section from a production one. To generate a valid schema for any layout in seconds, use the Schema Generator; to look up the Liquid that reads these settings, keep the cheatsheet handy.
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, 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
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.



