Liquid is Shopify's templating language - a Ruby-influenced syntax for rendering dynamic content in HTML. If you've worked with Twig, Handlebars, or Jekyll templates, Liquid will feel familiar. If you're coming from PHP or JavaScript, the main mental shift is that Liquid is intentionally limited: you can't write arbitrary code, only render data that Shopify exposes through specific objects.
This guide covers the architectural layer above the syntax - how Shopify's template system is organized, what Online Store 2.0 changed, and how to think about sections, blocks, and metafields when building or customising a theme.
The rendering hierarchy
Every Shopify page render passes through four layers:
- Layout file (
layout/theme.liquid) - The persistent HTML wrapper rendered on every page. Contains your<head>, navigation, footer, and the{{ content_for_layout }}placeholder where page content is inserted. - Template file (
templates/*.liquidortemplates/*.json) - Determines what content renders incontent_for_layoutbased on the page type. One template per page type:index(homepage),product,collection,page,blog,article,cart,404, etc. - Sections (
sections/*.liquid) - Reusable, configurable content blocks. Included into templates. Each section has its own Liquid logic, HTML, CSS, and a{% schema %}block that defines the settings merchants can edit in the Shopify theme editor. - Snippets (
snippets/*.liquid) - Reusable code fragments, included into layouts, templates, or sections with{% render 'snippet-name' %}. Unlike sections, snippets have no schema and can't be added/removed by merchants in the editor.
The difference between .liquid and .json templates
Online Store 2.0 (introduced with Dawn in 2021) changed how templates work. Previously, templates were .liquid files containing both HTML and Liquid logic. In OS 2.0, templates become .json files that reference an ordered list of sections. The templates/product.json file doesn't contain HTML - it contains a configuration of which sections appear on the product page and in what order:
{
"sections": {
"main": {
"type": "main-product",
"settings": {}
},
"product-recommendations": {
"type": "product-recommendations",
"settings": {
"heading": "You might also like",
"products_to_show": 4
}
}
},
"order": ["main", "product-recommendations"]
}
This is what enables merchants to add, remove, and reorder sections on any page type in the theme editor - not just the homepage. Before OS 2.0, only the homepage supported customizable sections.
Section schema: the settings system
Every section's {% schema %} block defines what settings merchants can configure in the Shopify admin. Understanding schema is essential for building themes that non-developers can actually maintain.
{% schema %}
{
"name": "Featured product",
"settings": [
{
"type": "product",
"id": "product",
"label": "Product"
},
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Featured this week"
},
{
"type": "checkbox",
"id": "show_vendor",
"label": "Show vendor",
"default": false
}
],
"presets": [
{
"name": "Featured product"
}
]
}
{% endschema %}
The presets array controls whether the section appears in the "Add section" panel in the theme editor. If there's no presets entry, the section can't be added by merchants (it can only be included programmatically in a template JSON or referenced from another section).
Blocks: repeatable items within sections
Blocks are the unit below sections - repeatable, orderable items within a section. A testimonial slider section might define a "testimonial" block type with text, author name, and photo fields. Merchants can add as many testimonial blocks as they want, reorder them, and delete them - all within the same section instance.
"blocks": [
{
"type": "testimonial",
"name": "Testimonial",
"settings": [
{ "type": "textarea", "id": "quote", "label": "Quote" },
{ "type": "text", "id": "author", "label": "Author name" },
{ "type": "image_picker", "id": "photo", "label": "Photo" }
]
}
],
"max_blocks": 10
In the section's Liquid, you iterate over section.blocks to render each block instance.
Metafields: custom data beyond product descriptions
Metafields let you attach structured custom data to any Shopify resource: products, variants, collections, orders, customers, pages. In OS 2.0 themes, metafields are directly accessible as dynamic sources in the theme editor - a merchant can connect a "specifications" metafield to a text block without writing Liquid.
Common uses:
- Product metafields: material, care instructions, size guide URL, nutritional information, certifications, compatible accessories
- Variant metafields: colour hex code for a custom swatch, lead time for a specific variant, subtitle text
- Collection metafields: hero image, banner text, category description for SEO, featured brand logos
In Liquid, access a product metafield with namespace and key:
{{ product.metafields.custom.material }}
Or iterate over a list metafield (a metafield with type "list.single_line_text_field"):
{% for feature in product.metafields.custom.features.value %}
<li>{{ feature }}</li>
{% endfor %}
Metaobjects: structured custom content types
Metaobjects (introduced in 2022) extend metafields into full custom content types. Where a metafield is an attribute on an existing resource, a metaobject is a standalone custom resource with its own fields. Examples: team members, press mentions, FAQ entries, size guides, ingredient lists.
A "size_guide" metaobject might have fields: garment_type, size_chart_image, measurement_instructions, and size_conversion_table. Products reference this metaobject, and the theme renders it as a drawer or modal. This replaces patterns that previously required custom apps or complex structured Liquid data.
Section groups: header and footer customization
Section groups (OS 2.0) extend the customizable sections model to the header and footer - areas that were previously hardcoded in theme.liquid. With section groups, the header and footer are defined in config/settings_data.json and rendered via {% sections 'header-group' %} in the layout file.
The practical impact: merchants can add announcement bars, promotional headers, and custom footer columns from the theme editor without a developer - and developers can build multiple header/footer variants that merchants switch between without theme code changes.
Performance considerations in Liquid
Render vs include
Always use {% render %} over the deprecated {% include %}. The key difference: render creates an isolated scope - the snippet can't access variables from the parent template. This prevents unintended side effects and makes snippets more predictable and reusable. It also allows Shopify to potentially parallelize snippet rendering in the future.
Section-specific CSS
Put section styles in {% stylesheet %} blocks within the section file rather than in a global CSS file. Shopify only outputs stylesheet blocks from sections that are active on the current page - so a promotional banner section's CSS doesn't load on product pages where the banner doesn't render. This is Shopify's built-in CSS splitting mechanism.
Lazy-load images below the fold
Use loading: 'lazy' in image_tag for all images below the fold. For the first image a visitor sees (hero, product featured image), use loading: 'eager' with fetchpriority: 'high' to start the download immediately without waiting for the lazy-load intersection observer.
Working with the Storefront API alongside Liquid
Liquid handles server-side rendering. For client-side dynamic behaviour - cart mutations, variant selection, wishlist toggles, real-time stock checks - you use JavaScript alongside the Storefront API or Shopify's AJAX API. These are two different APIs:
- AJAX API (
/cart.js,/cart/add.js,/products/{handle}.js) - Simple JSON endpoints for cart and product data. No auth required. Fast and sufficient for most in-theme JavaScript needs. - Storefront API (GraphQL) - More powerful, used for headless storefronts and complex client-side data fetching. Required for Hydrogen. Requires an access token scoped to the storefront.
For a traditional Shopify theme, the AJAX API handles 90% of JavaScript needs. The Storefront API is for headless builds or specific use cases like building a custom cart drawer that needs richer data than the AJAX cart returns.
Shopify's Liquid system has evolved significantly since Online Store 2.0 - the combination of JSON templates, section groups, metaobjects, and dynamic sources has made it far more powerful for non-developers while adding architectural complexity that developers need to understand before building on. Getting the architecture right early prevents the kind of accumulated technical debt that makes themes hard to maintain as the store grows.