Building custom sections and blocks in Shopify Online Store 2.0 (with code)
By Nabeel Yaseen, Senior Shopify Developer
Published · 5 min read
A custom Online Store 2.0 section is one Liquid file in the theme's sections folder: markup at the top, and a {% schema %} tag that defines its settings, blocks and presets. A page template can render up to 25 sections with up to 50 blocks each, and merchants arrange them in the theme editor without touching code.
What did Online Store 2.0 change?
Shopify announced Online Store 2.0 at Unite on 29 June 2021. The headline change was JSON templates: every page type, from products to blog posts, became a list of sections that merchants can add, remove and reorder. The same release brought app blocks, which let apps place their content inside a theme's sections, and Dawn, Shopify's open-source reference theme.
The model still holds in 2026. Shopify has since added theme blocks, and in July 2026 it previewed a way to compose pages directly in Liquid templates. The changelog for that preview says existing themes keep working with sections, theme settings and JSON templates, so sections are still where most theme work happens.
What goes into a section file?
| Part | What it does | Rule to remember |
|---|---|---|
{% schema %} |
Defines the name, settings, blocks and presets | One per file, valid JSON only, never inside another Liquid tag |
settings |
Inputs that apply to the whole section | Setting IDs must be unique within the section |
blocks |
Repeatable items merchants add, remove and reorder | 50 per section at most; max_blocks sets a lower cap |
presets |
Lists the section in the "Add section" picker | Without a preset, it can only be added by editing JSON |
enabled_on or disabled_on |
Limits the templates or section groups it can go in | Use one of the two, not both |
limit |
Caps how many times it can appear on a template | Only 1 or 2 |
@app block type |
Lets app blocks sit inside the section | Output them with {% render block %} |
All of these rules come from Shopify's section schema reference, which covers each attribute with examples.
A complete example: an FAQ accordion section
This is a section I'd be happy to hand to a client: questions and answers the merchant edits in the theme editor, inside an accordion that works without JavaScript.
{%- comment -%} sections/faq-accordion.liquid {%- endcomment -%}
<div class="faq" style="--faq-padding: {{ section.settings.padding }}px;">
{%- if section.settings.heading != blank -%}
<h2 class="faq__heading">{{ section.settings.heading }}</h2>
{%- endif -%}
{%- for block in section.blocks -%}
{%- case block.type -%}
{%- when 'question' -%}
<details class="faq__item" {{ block.shopify_attributes }}{% if forloop.first and section.settings.open_first %} open{% endif %}>
<summary class="faq__question">{{ block.settings.title | escape }}</summary>
<div class="faq__answer">{{ block.settings.answer }}</div>
</details>
{%- when '@app' -%}
{% render block %}
{%- endcase -%}
{%- endfor -%}
</div>
{% stylesheet %}
.faq { padding-block: var(--faq-padding); }
.faq__item { border-bottom: 1px solid rgb(0 0 0 / 0.15); }
.faq__question { cursor: pointer; padding: 1rem 0; font-weight: 600; }
.faq__answer { padding-bottom: 1rem; }
{% endstylesheet %}
{% schema %}
{
"name": "FAQ accordion",
"tag": "section",
"class": "section-faq",
"max_blocks": 20,
"disabled_on": { "groups": ["header", "footer"] },
"settings": [
{ "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Questions" },
{ "type": "checkbox", "id": "open_first", "label": "Open the first question", "default": true },
{ "type": "range", "id": "padding", "label": "Vertical padding", "min": 0, "max": 80, "step": 4, "unit": "px", "default": 36 }
],
"blocks": [
{
"type": "question",
"name": "Question",
"settings": [
{ "type": "text", "id": "title", "label": "Question", "default": "Do you ship worldwide?" },
{ "type": "richtext", "id": "answer", "label": "Answer", "default": "<p>Yes. Orders leave the warehouse within two working days.</p>" }
]
},
{ "type": "@app" }
],
"presets": [
{
"name": "FAQ accordion",
"blocks": [{ "type": "question" }, { "type": "question" }, { "type": "question" }]
}
]
}
{% endschema %}
A few details in that file are easy to miss:
- The question setting has the ID
title. The theme editor looks for a setting with the IDheading,titleortextand uses its value as the block's label, so the sidebar lists the actual questions instead of "Question" ten times over. - Each block's wrapper carries
{{ block.shopify_attributes }}. The editor reads those data attributes to work out which element belongs to which block when a merchant selects or moves one. <details>and<summary>make a native disclosure widget that opens with a click or the space bar, and every major browser has supported it since January 2020. With no script, there's nothing to re-initialise when the editor re-renders the section.- The CSS sits in a
{% stylesheet %}tag. Since 20 April 2026, Shopify only sends CSS from these tags for the sections, blocks and snippets rendered on the current page. The condition is that a class defined here is only used in this file or in snippets it renders. disabled_onkeeps the section out of the header and footer groups, andmax_blocksstops anyone building a 50-question wall.
What does the theme editor save?
When a merchant adds the section to a template, Shopify stores the content in that template's JSON file. Shopify generates the IDs; I've shortened them here so the structure is easier to read:
{
"sections": {
"faq": {
"type": "faq-accordion",
"blocks": {
"q1": { "type": "question", "settings": { "title": "Do you ship worldwide?" } },
"q2": { "type": "question", "settings": { "title": "How do returns work?" } }
},
"block_order": ["q1", "q2"],
"settings": { "heading": "Questions", "open_first": true, "padding": 36 }
}
},
"order": ["faq"]
}
So the content lives apart from the Liquid. You can rewrite the markup as often as you like, but if you rename a setting ID, the saved values stay under the old key and the section stops reading them. Shopify's docs also warn against code that checks a block's ID, because the IDs are generated and can change.
A checklist before a section goes live
- Every block wrapper has
{{ block.shopify_attributes }}. - The section has at least one preset with realistic default content.
- Anything a merchant might change is a setting or a block, never hard-coded text.
- JavaScript re-runs on the editor's
shopify:section:loadevent and cleans up onshopify:section:unload(see Shopify's theme editor events). - Editor-only behaviour checks
request.design_modein Liquid orShopify.designModein JavaScript. - Images go through
image_urlandimage_tag, and an image in the first section is never lazy-loaded. I explain why in why your Shopify store is slow. - Headings fit the page outline: an H2 under the page's H1, not a second H1.
- It has been tested in the theme editor: add, reorder, hide, remove and undo.
How I build sections on client stores
At Gablr I built more than 20 stores from Figma designs on Online Store 2.0. Every section on them was editable by the merchant, and each store launched at Lighthouse 90 or above.
The rule I work to: if the merchant might want to change it next month, it becomes a setting or a block, and each realistic layout gets its own preset. Making stores that merchants can edit themselves is one of the parts of Shopify work I enjoy most, and it saves clients from paying a developer to change a sentence.
Section blocks or theme blocks?
The FAQ above uses section blocks: the question block is defined inside the section's schema and exists only there. That suits content that belongs to one section. Section blocks can't nest, can't be reused elsewhere, and a section that uses them can't also accept theme blocks.
Theme blocks live in their own files in a blocks folder, work in any section that accepts them, and nest up to eight levels deep. Shopify built its Horizon themes on them. For a new section library I'd make generic pieces such as headings, buttons and images into theme blocks, and keep section blocks for items like FAQ rows. When the content should come from the product itself, such as a care guide or a size chart, use metafields and dynamic sources, which connect section settings to product data without extra code.
When you don't need a developer
Check what your theme already has before paying for a custom section. Dawn ships with a Collapsible content section that covers much of what my FAQ example does. Dawn and Horizon both include a Custom Liquid section for small snippets, and in any Theme Store theme eligible merchants can ask Sidekick to generate a theme block from a plain-text description. For spacing and colour tweaks, the theme editor has a custom CSS setting at the theme and section level.
A developer earns their fee when a section has to read product data, appear on many templates, stay fast with a lot of content, or match a designer's layout exactly. Merchants' content gets saved against the schema, so a poor schema decision is expensive to undo later. If you're budgeting for this kind of work, see how much a custom Shopify theme costs.
Sections like this, built and tested on your store, are the core of my custom Shopify theme development service.
- Shopify
- Liquid
- Themes
FAQ
Questions
A JSON template or a section group can render up to 25 sections, and each section can hold up to 50 blocks. You can set a lower cap for one section with max_blocks.
Its schema needs a presets array. Without a preset, the section can only be added by editing the template's JSON file, and merchants can't remove it in the theme editor.
Section blocks suit content that only belongs in one section, such as FAQ questions. Theme blocks suit pieces you want to reuse across sections or nest inside each other. One section can't use both kinds.
No. The HTML details and summary elements open and close without a script, and every major browser has supported them since January 2020.
Yes. Add a block of type @app to the section's schema and output it with {% render block %}. App blocks then appear in that section's block picker.
Related services
More from the journal
· 6 min read
How much does a custom Shopify theme cost?
A custom Shopify theme from Figma takes 60 to 120 hours, $2,700 to $5,400 at $45/hr. Theme Store themes cost $100 to $500. What sets the price.
- Pricing
- Themes
· 6 min read
Freelance Shopify developer or agency: which should you hire?
When a freelance Shopify developer is the better hire, when an agency is, and how cost, risk and communication compare, from a developer who has done both.
- Hiring
- Pricing
· 6 min read
Hiring a Shopify developer in another time zone: how US-hours overlap works
How time-zone overlap works with a Shopify developer in Pakistan (UTC+5): exact hours for US Eastern, Central, Pacific and UK teams, in summer and winter.
- Hiring
- Remote Work
Next step
Custom Shopify theme development
I build custom Online Store 2.0 themes from your design, from $2,700 (60 hours at $45), usually in 3 to 5 weeks.
