Shopify Online Store 2.0 Theme Architecture: The Developer's Complete Reference
Online Store 2.0 shipped in June 2021 and fundamentally rewired how Shopify themes work. JSON templates replaced Liquid templates as the default. Sections became available on every page, not just the homepage. App blocks replaced the messy script injection pattern. And native metafields eliminated the need for third-party metafield apps.
Five years later, most developers understand the concept. But we still see agencies delivering themes where the product page is a product.liquid file with hardcoded sections, the blog template has no block support, and metafields still require a third-party app. That's a 1.0 theme in a 2.0 folder structure.
This is the reference we give every new developer joining our Shopify development team at Innovatrix. It covers the architectural decisions that actually save time on production builds — not the theory, the practice.
The Quick Test: Is Your Theme Actually 2.0?
Open your theme's /templates folder. If you see product.liquid instead of product.json, you are running legacy architecture — regardless of what the theme claims.
A proper 2.0 theme has:
/templates/*.jsonfiles (not.liquid)/sections/directory with modular, reusable sections{% schema %}blocks in every section with properpresets- App block support via
@appblock type in schemas - No
{% include %}tags (deprecated — use{% render %})
JSON Templates vs. Liquid Templates
This is the single most important architectural change in 2.0.
Liquid templates (legacy): The template file contains HTML and Liquid code directly. The layout is hardcoded by the developer. Merchants cannot rearrange sections or add new ones.
{%- comment -%} templates/product.liquid — LEGACY, avoid this {%- endcomment -%}
<div class="product-page">
{% section 'product-hero' %}
{% section 'product-description' %}
{% section 'related-products' %}
</div>
JSON templates (2.0): The template file is a JSON data file that lists which sections appear and in what order. Merchants can add, remove, and reorder sections through the theme editor.
{
"sections": {
"main": {
"type": "main-product",
"settings": {}
},
"recommendations": {
"type": "product-recommendations",
"settings": {
"heading": "You may also like"
}
}
},
"order": ["main", "recommendations"]
}
Why this matters for your business: Every layout change on a Liquid template requires a developer. At ₹500-1,500 per edit (typical agency rate), a mid-size store making 8-20 layout changes per year spends ₹4,000-30,000 annually on changes that merchants could make themselves with a proper 2.0 theme. We've seen this pattern across our 50+ builds — the ROI of a 2.0 architecture pays for itself within months.
Sections Everywhere: What It Enables (and What It Breaks)
Before 2.0, sections only worked on the homepage. Every other page was a rigid Liquid template.
Now, every page type — product, collection, cart, blog, article, custom pages — supports sections. Merchants can build entirely different layouts for different product templates, collection templates, and page templates.
What this enables:
- Campaign-specific landing pages (create a new product template, add marketing sections)
- A/B testing different product page layouts without code changes
- Different collection page designs for different categories (a "Lookbook" collection template vs. a "Grid" collection template)
- Seasonal homepage redesigns in 10 minutes instead of a developer sprint
What it breaks in older themes:
- Themes with hardcoded CSS that assumes a specific section order will break when merchants rearrange sections
- JavaScript that targets specific DOM elements by position (
.product-page > div:nth-child(2)) will fail - Sections that depend on data from other sections can't guarantee their siblings exist
Our rule: Every section must be self-contained. It should function correctly regardless of which other sections are on the page, what order they appear in, or how many instances exist. This is the fundamental architectural principle of 2.0.
The Section/Block/Setting Hierarchy
Understanding the three-level hierarchy is essential for building maintainable themes.
Sections are the top-level containers. Each JSON template can have up to 25 sections. Sections define the overall layout unit (hero banner, product grid, testimonial carousel).
Blocks live inside sections and represent repeatable content units. Each section can contain up to 50 blocks. Blocks are the individual items within a section (a single testimonial, a single feature card, a single FAQ item).
Settings exist on both sections and blocks. They're the configuration fields merchants interact with in the theme editor (text inputs, colour pickers, image selectors, range sliders).
{% schema %}
{
"name": "Testimonials",
"tag": "section",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Section Heading",
"default": "What Our Customers Say"
}
],
"blocks": [
{
"type": "testimonial",
"name": "Testimonial",
"settings": [
{
"type": "richtext",
"id": "quote",
"label": "Quote"
},
{
"type": "text",
"id": "author",
"label": "Author Name"
},
{
"type": "image_picker",
"id": "avatar",
"label": "Author Photo"
}
]
}
],
"presets": [
{
"name": "Testimonials",
"blocks": [
{ "type": "testimonial" },
{ "type": "testimonial" },
{ "type": "testimonial" }
]
}
]
}
{% endschema %}
Rendering blocks in Liquid:
<div class="testimonials">
<h2>{{ section.settings.heading }}</h2>
{% for block in section.blocks %}
<div class="testimonial" {{ block.shopify_attributes }}>
<blockquote>{{ block.settings.quote }}</blockquote>
<cite>
{% if block.settings.avatar %}
{{ block.settings.avatar | image_url: width: 60 | image_tag: class: 'avatar' }}
{% endif %}
{{ block.settings.author }}
</cite>
</div>
{% endfor %}
</div>
Note the {{ block.shopify_attributes }} — this outputs data-shopify-editor-block attributes that enable the theme editor to identify and highlight individual blocks. Always include this on your block container element.
App Blocks vs. Theme App Extensions
This is the distinction most developers get wrong.
Theme App Extensions are the developer-side framework. App developers build extensions using Shopify CLI that define app blocks, app embed blocks, and app snippets.
App Blocks are the merchant-facing result. They appear in the theme editor as draggable blocks that merchants can add to sections — just like native theme blocks.
The critical benefit: when a merchant uninstalls an app, its app blocks are automatically removed from the theme. No orphaned <script> tags silently slowing the store. No ghost code from uninstalled apps inflating your Total Blocking Time.
To support app blocks in your section, add the @app block type:
{
"blocks": [
{
"type": "testimonial",
"name": "Testimonial"
},
{
"type": "@app"
}
]
}
Our rule: Every section that could logically contain third-party functionality (product pages, cart, collection pages) should include @app in its blocks array. The cost is zero, and it future-proofs the theme for any app the merchant installs.
content_for_header and content_for_layout
These are required globals in your layout/theme.liquid file.
<!DOCTYPE html>
<html>
<head>
{{ content_for_header }}
{%- comment -%} Your CSS, meta tags, etc. go here {%- endcomment -%}
</head>
<body>
{{ content_for_layout }}
</body>
</html>
content_for_header outputs essential Shopify scripts: analytics, Shop Pay, consent management, preview bar, and app scripts. Never remove this. If you do, checkout breaks, analytics stop tracking, and app blocks won't render.
content_for_layout renders the current template's sections. This is where your JSON template content appears.
Performance note: content_for_header adds ~50-80KB of JavaScript to every page. You cannot remove it, but you can defer your own scripts to avoid competing for the main thread. Load your custom JS with defer or at the bottom of <body>, never in <head> alongside content_for_header.
Theme Check: Your CI Pipeline Linter
Theme Check is Shopify's official linter for themes. It catches errors that Liquid won't (remember, Liquid fails silently).
Install:
npm install -g @shopify/theme-check
Run:
shopify theme check --path ./theme
What it catches:
- Deprecated
{% include %}tags - Missing
{{ content_for_header }}or{{ content_for_layout }} - Invalid schema JSON
- Missing
altattributes on images - Unused variables
- Performance anti-patterns (unused CSS, large inline scripts)
We run Theme Check in our CI pipeline on every pull request. Any theme that fails the linter doesn't get merged. This single practice has eliminated an entire class of "it works on my machine" bugs.
Performance Implications of Section Architecture
Section structure directly affects rendering performance. Each section is rendered independently, which is generally good for caching. But there are traps.
Asset loading order matters. Sections load their CSS and JS in the order they appear in the JSON template. If a below-the-fold section loads a heavy JS library, it blocks rendering of everything after it.
Our approach:
- Critical sections (header, hero, above-the-fold product info) load CSS inline via
{% style %}tags - Below-the-fold sections use
<link rel="stylesheet" href="..." media="print" onload="this.media='all'">for deferred CSS loading - JavaScript for interactive sections uses
IntersectionObserverto initialise only when the section enters the viewport - We never load external carousel libraries (Swiper, Slick). Vanilla JS + CSS scroll-snap handles 95% of carousel needs at a fraction of the weight
The real test: Performance on a mid-range Android phone (₹8,000-12,000 range) on a 3G connection. That's where your actual customers are in India. Test there, not on your MacBook Pro on fibre.
When we built FloraSoul India's Shopify store using these architecture principles, the performance optimisation contributed to their +41% mobile conversion improvement. Architecture decisions compound — every millisecond of render time you save across every page load adds up to real revenue.
A Complete Section Template (Copy-Paste Ready)
This is the section template we start every new section from at Innovatrix. It includes all the architectural best practices covered in this guide:
{%- comment -%}
Section: Featured Content
Description: A flexible content section with heading, text, and image
Author: Innovatrix Infotech
{%- endcomment -%}
{%- liquid
assign section_id = section.id
assign heading = section.settings.heading
assign padding_top = section.settings.padding_top | default: 40
assign padding_bottom = section.settings.padding_bottom | default: 40
-%}
{%- style -%}
.section-{{ section_id }} {
padding-top: {{ padding_top }}px;
padding-bottom: {{ padding_bottom }}px;
}
@media (min-width: 750px) {
.section-{{ section_id }} {
padding-top: {{ padding_top | times: 1.5 | round }}px;
padding-bottom: {{ padding_bottom | times: 1.5 | round }}px;
}
}
{%- endstyle -%}
<section class="section-{{ section_id }}" id="section-{{ section_id }}">
<div class="page-width">
{%- if heading != blank -%}
<h2 class="section__heading">{{ heading }}</h2>
{%- endif -%}
<div class="section__blocks">
{%- for block in section.blocks -%}
{%- case block.type -%}
{%- when 'text' -%}
<div class="block block--text" {{ block.shopify_attributes }}>
{{ block.settings.text }}
</div>
{%- when 'image' -%}
<div class="block block--image" {{ block.shopify_attributes }}>
{%- if block.settings.image != blank -%}
{{ block.settings.image | image_url: width: 800 | image_tag:
loading: 'lazy',
widths: '375,550,750,800',
alt: block.settings.image.alt
}}
{%- endif -%}
</div>
{%- when '@app' -%}
<div class="block block--app" {{ block.shopify_attributes }}>
{% render block %}
</div>
{%- endcase -%}
{%- endfor -%}
</div>
</div>
</section>
{% schema %}
{
"name": "Featured Content",
"tag": "section",
"class": "section-featured-content",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading"
},
{
"type": "range",
"id": "padding_top",
"label": "Top padding",
"min": 0,
"max": 100,
"step": 4,
"unit": "px",
"default": 40
},
{
"type": "range",
"id": "padding_bottom",
"label": "Bottom padding",
"min": 0,
"max": 100,
"step": 4,
"unit": "px",
"default": 40
}
],
"blocks": [
{
"type": "text",
"name": "Text Block",
"settings": [
{
"type": "richtext",
"id": "text",
"label": "Content"
}
]
},
{
"type": "image",
"name": "Image",
"settings": [
{
"type": "image_picker",
"id": "image",
"label": "Image"
}
]
},
{
"type": "@app"
}
],
"presets": [
{
"name": "Featured Content",
"blocks": [
{ "type": "text" }
]
}
]
}
{% endschema %}
For the Liquid patterns used inside sections, see our companion guide: Shopify Liquid Developer Reference.
Frequently Asked Questions
Written by

Founder & CEO
Rishabh Sethia is the founder and CEO of Innovatrix Infotech, a Kolkata-based digital engineering agency. He leads a team that delivers web development, mobile apps, Shopify stores, and AI automation for startups and SMBs across India and beyond.
Connect on LinkedIn