Shopify themes built for Online Store 2.0 give merchants something older themes couldn't: they can add, remove and reorder sections on every page, not just the homepage, without asking a developer. For developers, that changes the job. Instead of hard-coding layouts, you build flexible sections with sensible settings, and the merchant composes pages from them.
Fashion and gifting stores such as Threads of Harman and Glamour Rose are typical Shopify builds where this flexibility matters, because campaigns and collections change every few weeks.
How a 2.0 theme is organizedLink to this section
theme/
├── layout/theme.liquid the HTML shell
├── templates/
│ ├── product.json which sections a product page uses, in order
│ ├── collection.json
│ └── page.contact.json alternate templates per page
├── sections/ reusable sections with {% schema %}
├── blocks/ theme blocks, reusable across sections
├── snippets/ partials rendered with {% render %}
├── assets/ CSS, JS, images
├── config/settings_schema.json global theme settings
└── locales/The key change is JSON templates. A template is no longer Liquid markup but a list of sections and their settings:
{
"sections": {
"main": { "type": "main-product", "settings": {} },
"badges": {
"type": "trust-badges",
"blocks": {
"b1": { "type": "badge", "settings": { "title": "Free delivery over ₹999" } },
"b2": { "type": "badge", "settings": { "title": "7-day easy returns" } }
},
"block_order": ["b1", "b2"]
}
},
"order": ["main", "badges"]
}Merchants edit this through the theme editor, and Shopify writes the JSON. When you pull a live theme to inspect it, the JSON formatter makes these files and settings_data.json much easier to read.
Building a reusable sectionLink to this section
A section is Liquid markup plus a {% schema %} that defines its settings, blocks and presets. Here is a trust-badges section that merchants can add to any page:
<div class="trust-badges page-width">
{%- for block in section.blocks -%}
<div class="trust-badge" {{ block.shopify_attributes }}>
{%- if block.settings.icon != blank -%}
{{ block.settings.icon | image_url: width: 96 | image_tag: loading: 'lazy', alt: block.settings.title }}
{%- endif -%}
<p>{{ block.settings.title | escape }}</p>
</div>
{%- endfor -%}
</div>
{% schema %}
{
"name": "Trust badges",
"tag": "section",
"class": "section",
"max_blocks": 6,
"settings": [
{ "type": "color_scheme", "id": "color_scheme", "label": "Color scheme", "default": "scheme-1" }
],
"blocks": [
{
"type": "badge",
"name": "Badge",
"settings": [
{ "type": "image_picker", "id": "icon", "label": "Icon" },
{ "type": "text", "id": "title", "label": "Text", "default": "Free delivery" }
]
}
],
"presets": [
{ "name": "Trust badges", "blocks": [ { "type": "badge" }, { "type": "badge" } ] }
]
}
{% endschema %}What each part does:
blockslets merchants add up tomax_blocksbadges and reorder them.{{ block.shopify_attributes }}connects each element to the theme editor so clicking it selects the right block. Don't forget it.presetsmakes the section appear in "Add section", pre-filled with two badges.image_url+image_tagrequest an appropriately sized image from Shopify's CDN with lazy loading and alt text.
The color_scheme setting only works if the theme defines color schemes in settings_schema.json, as Dawn-based themes do. Adapt the settings to the theme you're extending.
Theme blocks: one block, many sectionsLink to this section
Blocks defined inside a section's schema only exist in that section. Theme blocks are different: each one is its own Liquid file in the blocks/ folder, with its own schema, and any section that accepts them can use it. Merchants can also nest theme blocks inside each other, for example a column block that holds a heading, text and a button.
A section opts in by accepting the @theme block type and rendering whatever the merchant added with content_for:
{%- comment -%} sections/feature-row.liquid {%- endcomment -%}
<div class="feature-row page-width">
{% content_for 'blocks' %}
</div>
{% schema %}
{
"name": "Feature row",
"blocks": [ { "type": "@theme" }, { "type": "@app" } ],
"presets": [ { "name": "Feature row" } ]
}
{% endschema %}{%- comment -%} blocks/badge.liquid {%- endcomment -%}
<div class="badge" {{ block.shopify_attributes }}>
<p>{{ block.settings.title | escape }}</p>
</div>
{% schema %}
{
"name": "Badge",
"settings": [ { "type": "text", "id": "title", "label": "Text", "default": "Free delivery" } ],
"presets": [ { "name": "Badge" } ]
}
{% endschema %}A section uses either its own section-defined blocks or theme blocks, so choose one approach per section. Theme blocks pay off when the same small pieces (badges, buttons, icon-with-text) appear in many sections; section blocks stay simpler for one-off layouts.
Section groups for the header and footerLink to this section
Headers and footers live in section groups: sections/header-group.json and sections/footer-group.json, rendered in layout/theme.liquid with {% sections 'header-group' %}. Because they are groups rather than fixed includes, merchants can add an announcement bar or a newsletter section to the header or footer area without a developer. Use enabled_on or disabled_on in a section's schema to control which templates and groups it can be added to.
Metafields: product-specific content without hacksLink to this section
Size guides, fabric care instructions and delivery notes differ per product. Instead of encoding them in descriptions, define metafields (Settings → Custom data → Products) and read them in Liquid:
{%- assign care = product.metafields.custom.care_instructions -%}
{%- if care != blank -%}
<details class="product-care">
<summary>Care instructions</summary>
{{ care | metafield_tag }}
</details>
{%- endif -%}Merchants can also connect metafields to section settings directly in the theme editor ("dynamic sources"), so a well-designed section works for every product without new code.
App blocks instead of injected scriptsLink to this section
Apps built for 2.0 provide app blocks that merchants place with the theme editor. To support them in your section, allow the @app block type:
"blocks": [ { "type": "@app" }, { "type": "badge", "name": "Badge", "settings": [] } ]This is far cleaner than apps editing theme files, and uninstalling an app no longer leaves orphaned code behind.
A safe development workflow with Shopify CLILink to this section
# Live-reloading preview against a development theme (doesn't touch the live theme)
shopify theme dev --store your-store.myshopify.com
# Pull the current live theme to compare or start from
shopify theme pull --store your-store.myshopify.com
# Lint Liquid, JSON and performance issues
shopify theme check
# Upload as an unpublished theme for client review
shopify theme push --unpublished --store your-store.myshopify.comKeep the theme in Git, and use Shopify's GitHub integration if the store team wants every change deployed from a branch. One caution: merchants change JSON templates in the editor, so pull before you push to avoid overwriting their work.
Performance and accessibilityLink to this section
Shopify handles hosting and the CDN, but themes can still be slow:
- Watch app scripts. Every installed app may add JavaScript to every page. Remove apps that aren't earning their keep.
- Size images on purpose with
image_url: width:andsizes, and prefer one strong hero over a heavy slideshow. For assets you upload yourself, convert to WebP first with the image converter. - Check color contrast of text on banners and buttons. Brand palettes often fail WCAG on light backgrounds. The contrast checker gives you the ratio and a pass or fail.
The Core Web Vitals guide is written for WordPress, but the LCP, INP and CLS principles apply to Shopify themes too.
Common problems and how to fix themLink to this section
| Problem | Usual cause | Fix |
|---|---|---|
| A section doesn't appear under "Add section" | No presets in its schema, or enabled_on / disabled_on excludes this template |
Add a preset; check the template and group rules |
| Clicking an element in the preview doesn't select its block | Missing {{ block.shopify_attributes }} on the block's wrapper |
Add it to the outermost element of each block |
| The merchant's edits disappeared after a deploy | Local templates/*.json or config/settings_data.json overwrote editor changes |
shopify theme pull first, or push with --ignore "templates/*.json" --ignore "config/settings_data.json" |
| The editor refuses to add more sections | A JSON template can hold up to 25 sections, and a section up to 50 blocks | Combine related content into one section with blocks |
| Product images look soft on retina screens | The requested image_url width is the display width |
Request roughly twice the displayed width and let image_tag output srcset |
| "Liquid error" text shows on the page | A filter received nil, often an empty metafield |
Guard with != blank and run shopify theme check before every push |
Most of these show up the first time a merchant uses the theme editor, so test the editor itself, not only the storefront, before handing a theme over.
Shopify or WooCommerce?Link to this section
Shopify wins on managed hosting, checkout reliability and speed to launch. WooCommerce wins on flexibility, data ownership and deep customization. Customizing WooCommerce without breaking updates shows the WordPress side. Many businesses are best served by whichever platform their team can run day to day.
Frequently asked questionsLink to this section
Can an older "vintage" theme be upgraded to Online Store 2.0?Link to this section
Yes. Each Liquid template is converted into a "main" section, and a JSON template is created that references it. Shopify documents the process. For heavily customized vintage themes, moving the store to a current theme such as Dawn and rebuilding the custom pieces as sections is often faster and leaves a cleaner codebase.
Do I need React or Hydrogen to build a Shopify theme?Link to this section
No. Online Store 2.0 themes are Liquid, HTML, CSS and a little JavaScript. Hydrogen is Shopify's React framework for fully custom, headless storefronts. It's worth considering only when the storefront needs app-like behaviour that a theme can't provide, because it brings its own hosting and maintenance.
How do I stop merchants from breaking the layout?Link to this section
Expose only the settings that make sense. Use select and range settings with sensible defaults instead of free text, cap blocks with max_blocks, and keep spacing and typography in the theme's global settings rather than in every section.
How do I test a theme before it goes live?Link to this section
Run shopify theme dev for a live-reloading preview, then push an unpublished theme and review it in the theme editor on desktop and mobile. Share the preview link with the store owner so they can try the editor themselves before you publish.
If you need a Shopify section built, a theme customized or a store tidied up for speed, send me your store URL and goals.
Test yourself
3 quick questions on this lesson.
Finished this lesson?
Mark it complete to track your progress through the learning paths.