Sections & Blocks

What makes a section different from a plain Liquid file is a {% schema %} tag at the bottom, declaring what settings and blocks the section exposes to the Theme Editor. That one tag is the entire bridge between a developer's code and a merchant's drag-and-drop customization.

Anatomy of a section

Liquid sections/announcement-bar.liquid
<div class="announcement-bar">
  <p>{{ section.settings.message }}</p>
</div>

{% schema %}
{
  "name": "Announcement bar",
  "settings": [
    {
      "type": "text",
      "id": "message",
      "label": "Message",
      "default": "Free shipping over $50"
    }
  ]
}
{% endschema %}

Whatever a merchant types into the "Message" field in the Theme Editor becomes available in the template as section.settings.message — the schema's id is the key that connects the two.

Blocks: repeatable sub-items

Some sections need a variable number of repeatable pieces — testimonials, FAQ entries, logo lists. That's what blocks are for: a merchant adds, removes, and reorders blocks in the Theme Editor, and the section loops over whatever blocks currently exist:

Liquid sections/testimonials.liquid
<div class="testimonials">
{% for block in section.blocks %}
  <blockquote {{ block.shopify_attributes }}>
    {{ block.settings.quote }}
  </blockquote>
{% endfor %}
</div>

{% schema %}
{
  "name": "Testimonials",
  "blocks": [
    {
      "type": "testimonial",
      "name": "Testimonial",
      "settings": [
        { "type": "textarea", "id": "quote", "label": "Quote" }
      ]
    }
  ]
}
{% endschema %}

{{ block.shopify_attributes }} injects hidden data attributes the Theme Editor needs to highlight and select that specific block on the page — it's easy to forget and harmless to include even where it's not strictly needed.

Reserving space for apps: Theme App Extensions

A block's type can also be "@app", which reserves a slot in a section that an installed app can drop its own block into. This is one half of what Shopify calls a Theme App Extension — a bundle of Liquid, CSS, and JavaScript that lives entirely inside an app's own codebase (not the theme's) and gets injected into whichever slots a merchant chooses, without the theme developer ever having to write app-specific code:

JSON allowing an app block in a schema
{
  "blocks": [
    { "type": "@app" },
    { "type": "text", "name": "Text" }
  ]
}

The other half is an app embed block — a similar app-owned piece of Liquid, but meant for something that doesn't need a specific position in a section, like a chat widget or an analytics snippet a merchant simply toggles on for the whole store from the Theme Editor's "App embeds" panel.

Static vs. dynamic sections

By default, a section added to a JSON template is "dynamic" — a merchant can add, remove, and reorder it freely. A section can instead be marked "enabled_on" with specific rules, or included directly in theme.liquid as a static section (using {% section 'header' %}) when it should always exist in a fixed position, like a header or footer that shouldn't be removable from the page.

Section groups

Headers and footers are handled slightly differently — they live in section groups (JSON files in sections/ ending in .json, like header-group.json) that are shared across every template automatically, rather than being placed page by page.

Note: settings and blocks aren't limited to text — the schema's type field also supports images, colors, checkboxes, product pickers, and more, all rendered as the appropriate control in the Theme Editor automatically.