Blocks are the smaller, repeatable pieces a merchant adds, reorders, and configures inside a section — a slide, a feature, a testimonial. For years blocks could only be defined inside a section. The modern architecture introduces theme blocks: standalone, reusable blocks that live in their own /blocks folder, can be shared across sections, and can even nest inside one another.
This guide explains how theme blocks work, how they differ from the older section blocks, and how to render and nest them. For the schema attributes that power all of this, keep the Section Schema reference nearby; to build block settings visually, use the Schema Builder.
Three kinds of blocks
It helps to name all three up front:
- Section blocks — defined locally inside a single section's
{% schema %}. Only work in that section, can't be reused, and can't nest. - Theme blocks — defined as their own files in
/blocks. Reusable across sections, configurable in the editor, and nestable. - App blocks — provided by installed apps so merchants can drop app content into a theme without editing code.
The big shift is theme blocks. They turn a block into a reusable component instead of something locked to one section.
Theme blocks vs section blocks
| Section blocks | Theme blocks | |
|---|---|---|
| Defined in | A section's schema | A file in /blocks |
| Reusable across sections | No | Yes |
| Nesting | Not allowed | Up to 8 levels deep |
| Mix in the same section | — | Can't mix with section blocks |
| Access parent section & globals | Yes | Yes |
| Receive variables from parent code | No | No |
One rule to internalize: you can't use theme blocks and locally-defined section blocks in the same section. A section commits to one model or the other.
Anatomy of a theme block
A theme block is a .liquid file in /blocks with its own markup and {% schema %}. For example, /blocks/feature.liquid:
<div class="feature" {{ block.shopify_attributes }}>
{% if block.settings.icon != blank %}
{{ block.settings.icon | image_url: width: 80 | image_tag }}
{% endif %}
<h3>{{ block.settings.title }}</h3>
<p>{{ block.settings.text }}</p>
</div>
{% schema %}
{
"name": "Feature",
"settings": [
{ "type": "image_picker", "id": "icon", "label": "Icon" },
{ "type": "text", "id": "title", "label": "Title", "default": "Feature" },
{ "type": "textarea", "id": "text", "label": "Text" }
]
}
{% endschema %}
Always include {{ block.shopify_attributes }} on the block's root element — it's what lets the theme editor select and highlight the block.
Accepting theme blocks in a section
A section opts in to theme blocks by listing block types in its schema and rendering them with {% content_for 'blocks' %}:
<div class="custom-section color-{{ section.settings.color_scheme }}">
{% content_for 'blocks' %}
</div>
{% schema %}
{
"name": "Custom section",
"blocks": [{ "type": "@theme" }, { "type": "@app" }],
"settings": [
{ "type": "color_scheme", "id": "color_scheme", "label": "Color scheme", "default": "scheme-1" }
],
"presets": [{ "name": "Custom section" }]
}
{% endschema %}
Two special block types do the heavy lifting:
@theme— accept any theme block (any file in/blocks).@app— accept app blocks from installed apps.
If you want to restrict a section to specific blocks instead of accepting everything, list them by name:
"blocks": [{ "type": "@app" }, { "type": "feature" }, { "type": "testimonial" }]
You can also include @theme alongside named blocks to recommend those specific blocks in the picker while keeping the rest available under "Show all."
{% content_for 'blocks' %} — the render tag
The single tag {% content_for 'blocks' %} outputs all of a section's blocks, in the order the merchant arranged them in the editor. You don't write a {% for block in section.blocks %} loop for theme blocks — content_for handles ordering and rendering.
That's a key difference from section blocks, where you do loop manually:
{# Section blocks (the older model) — manual loop #}
{% for block in section.blocks %}
{% case block.type %}
{% when 'text' %}
<p {{ block.shopify_attributes }}>{{ block.settings.body }}</p>
{% endcase %}
{% endfor %}
Nesting theme blocks
Theme blocks can contain other theme blocks — up to 8 levels deep (not counting the section). A block accepts children by declaring blocks in its own schema and rendering them with {% content_for 'blocks' %}:
{# /blocks/group.liquid #}
<div class="group" {{ block.shopify_attributes }}>
{% content_for 'blocks' %}
</div>
{% schema %}
{
"name": "Group",
"blocks": [{ "type": "@theme" }]
}
{% endschema %}
One limitation to remember: unlike sections, theme blocks can't define local blocks in their schema. They reference other blocks by type (@theme, @app, or a named block), but they can't declare a brand-new block inline.
Static blocks with content_for
Most blocks are dynamic — the merchant adds and reorders them. Sometimes you want a block in a fixed position that's always present. That's a static block, rendered by passing a type and id:
{% content_for "block", type: "announcement", id: "top-announcement" %}
Static blocks are useful for fixed layouts, conditional rendering, or placing a specific block exactly where your design needs it, rather than wherever the merchant drops it.
Presets with nested blocks
Presets define the default content a section ships with when a merchant adds it. With theme blocks, presets can pre-build a whole nested structure:
"presets": [
{
"name": "Heading and text",
"blocks": [
{
"type": "group",
"blocks": [
{ "type": "text", "settings": { "text": "<h2>Image with text</h2>" } },
{ "type": "text", "settings": { "text": "<p>Pair text with an image.</p>" } }
]
}
]
}
]
A good preset means a merchant gets a sensible, ready-to-edit section the moment they add it — a real usability win.
A short history note
If you maintain older themes, you may see @global in some section schemas. That type was replaced by @app — update any @global blocks to @app so app blocks render correctly. This is exactly the kind of deprecation covered in common Shopify development mistakes.
When to use which
- Theme blocks — your default for anything reusable or nestable: features, slides, content groups, cards. The modern, flexible choice.
- Section blocks — fine for simple, one-off blocks that only ever live in a single bespoke section.
- App blocks — always accept
@appin content sections so merchants can add app functionality without editing code.
Next steps
Theme blocks are how modern Shopify sections stay flexible and reusable. Build their schemas visually in the Schema Builder, turn an HTML component straight into a block with the HTML to Liquid converter, and look up the exact Liquid for objects and filters in the cheatsheet. To see how blocks fit into a full section, read How to Convert HTML into a Shopify Section.



