Skip to main content
Back to Blog
Theme Blocks in Shopify: The Modern Block Architecture
Tutorial

Theme Blocks in Shopify: The Modern Block Architecture

Theme blocks are reusable, nestable blocks that live in their own /blocks folder and render with content_for. Here's how they differ from section blocks, how nesting and presets work, and when to use each.

9 min read

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 @app in 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.

Found this helpful?

Share it with your network!

Ready to Convert HTML to Liquid?

Try our free HTML to Liquid converter and build your Shopify themes faster.

Try HTML2Liquid Now