0%

Shopify’s New Theme Architecture: What Changes for Developers

Shopify

If you’ve built Shopify themes for a while, your mental model probably looks like this: sections/ holds reusable Liquid modules, JSON templates reference them, and blocks live inside a section as repeatable sub-items — a slide in a carousel, a testimonial in a list. That model isn’t going away, but Shopify’s newest theme generation — the Horizon family, now the default for new stores — adds a real shift on top of it that’s worth understanding before you get handed a Horizon-based project.

What’s actually new: the blocks/ folder

Horizon-based themes introduce a top-level blocks/ directory, sitting alongside sections/. Inside it, each theme block is its own .liquid file with a schema, just like a section — but blocks are no longer confined to living inside one specific section. They’re standalone, reusable, and nestable inside each other, not just inside a parent section.

sections/
  hero.liquid
  featured-collection.liquid
blocks/
  button.liquid
  heading.liquid
  _icon.liquid

A theme block file looks structurally similar to a section — Liquid markup up top, {% schema %} at the bottom:

<button class="btn btn--{{ block.settings.style }}">
  {{ block.settings.label }}
</button>

{% schema %}
{
  "name": "Button",
  "settings": [
    { "type": "text", "id": "label", "label": "Label" },
    { "type": "select", "id": "style", "label": "Style", "options": [
      { "value": "primary", "label": "Primary" },
      { "value": "secondary", "label": "Secondary" }
    ]}
  ],
  "presets": [{ "name": "Button" }]
}
{% endschema %}

Any change to button.liquid now applies everywhere that block is used across the theme — one source of truth instead of copy-pasted markup living inside five different sections.

Nesting and private blocks

Blocks can now contain other blocks, which is the part that most changes how you architect a theme. A section can declare which block types it accepts via the blocks attribute in its schema, and a block can do the same for the blocks nested inside it.

If a block shouldn’t be dragged into the customizer everywhere — say, it’s an internal building block for one specific section — prefix the filename with an underscore to mark it private:

blocks/
  _icon.liquid    ← private: only usable where explicitly allowed

A private block only shows up where a parent section or block’s schema explicitly lists it, instead of cluttering the “Add block” menu across the entire theme.

Why this matters for how you build

  • Less duplication. A “heading” or “button” block written once can be reused across your hero, your product page, and your footer, instead of being redefined per section.
  • Merchant flexibility goes up. Merchants can now compose layouts by nesting blocks in the editor in ways a rigid section structure never allowed.
  • Schema complexity goes up too. With blocks nestable inside blocks inside sections, it’s easier to end up with deeply nested settings that are hard to reason about. Keep an eye on how many levels deep you actually need.
  • No automatic migration. If you’re moving a theme from the Dawn-style model to a Horizon-style one, there’s no one-click conversion — the underlying models are different enough that you’re rebuilding, not porting.

Should you migrate existing themes now?

Not urgently. sections/ isn’t deprecated, and Dawn-generation themes are still fully supported — this is additive architecture, not a breaking change forcing a rewrite. If you’re starting a new build or already doing a redesign, building on the newer theme block model is worth it for the reuse benefits. If you’re maintaining a mature Dawn-based theme that works fine, there’s no pressure to tear it apart just to chase the new structure.

Worth trying

Spin up a Horizon-based theme in a development store and poke through its blocks/ folder — seeing how a real, shipping theme composes nested blocks is a faster way to internalize the pattern than reading about it.