Skip to main content

Liquid Templating

Liquid is the templating language that powers every Shopify Online Store theme. Created by Shopify co-founder Tobias Lutke, Liquid renders HTML on the server using data from the store. Understanding Liquid is essential whether you are building themes, creating app blocks, or customizing storefronts for merchants. This lesson covers the language fundamentals, theme architecture, and performance best practices.

Liquid Fundamentals

Liquid has three building blocks: objects, tags, and filters.

Objects

Objects output data. They are wrapped in double curly braces:

{{ product.title }}
{{ product.price | money }}
{{ shop.name }}
{{ customer.first_name }}

Objects are provided by Shopify based on the current page context. On a product page, product is available. On a collection page, collection is available. Global objects like shop, cart, and settings are available everywhere.

Tags

Tags create logic and control flow. They are wrapped in curly braces with percent signs:

{% if product.available %}
<button type="submit">Add to Cart</button>
{% else %}
<button disabled>Sold Out</button>
{% endif %}

{% for product in collection.products %}
<div class="product-card">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</div>
{% endfor %}

{% unless customer %}
<a href="/account/login">Log in for exclusive pricing</a>
{% endunless %}

Common tags include:

TagPurpose
if / elsif / elseConditional logic
forIterate over arrays
unlessNegative conditional
case / whenSwitch statement
assignCreate a variable
captureCapture output into a variable
renderInclude a snippet
sectionRender a section
formGenerate Shopify forms
paginatePaginate a collection
commentCode comments (not rendered)

Filters

Filters modify the output of objects. They are separated by a pipe |:

{{ "hello world" | capitalize }}
<!-- Output: Hello world -->

{{ product.price | money_with_currency }}
<!-- Output: $29.99 USD -->

{{ product.title | handleize }}
<!-- Output: premium-widget -->

{{ product.description | strip_html | truncate: 150 }}
<!-- Output: First 150 characters, no HTML -->

{{ "now" | date: "%B %d, %Y" }}
<!-- Output: March 30, 2026 -->

Shopify provides dozens of filters specific to e-commerce:

<!-- Money formatting -->
{{ 2999 | money }} → $29.99
{{ 2999 | money_with_currency }} → $29.99 USD
{{ 2999 | money_without_trailing_zeros }} → $29.99

<!-- Image manipulation -->
{{ product.featured_image | image_url: width: 400 }}
{{ product.featured_image | image_url: width: 800, height: 600, crop: 'center' }}

<!-- URL generation -->
{{ product | product_url }}
{{ "cart" | link_to: routes.cart_url }}

<!-- Array operations -->
{{ collection.products | where: "available", true | size }}
{{ collection.products | sort: "price" | first }}
{{ collection.products | map: "title" | join: ", " }}

Theme Architecture

A Shopify theme is a structured collection of Liquid files, assets, and configuration. Understanding the file structure is critical.

theme/
├── layout/
│ └── theme.liquid # Main layout wrapper
├── templates/
│ ├── index.json # Homepage template
│ ├── product.json # Product page template
│ ├── collection.json # Collection page template
│ ├── page.json # Custom page template
│ ├── blog.json # Blog template
│ ├── article.json # Article template
│ ├── cart.json # Cart page template
│ ├── 404.json # Not found page
│ ├── search.json # Search results
│ └── customers/
│ ├── account.json # Customer account
│ ├── login.json # Login page
│ └── order.json # Order detail
├── sections/
│ ├── header.liquid # Site header
│ ├── footer.liquid # Site footer
│ ├── featured-collection.liquid
│ ├── product-info.liquid
│ └── rich-text.liquid
├── snippets/
│ ├── product-card.liquid # Reusable product card
│ ├── price.liquid # Price display logic
│ └── icon-cart.liquid # SVG icon
├── assets/
│ ├── theme.css
│ └── theme.js
├── config/
│ ├── settings_schema.json # Theme settings definition
│ └── settings_data.json # Current settings values
└── locales/
├── en.default.json # English translations
└── fr.json # French translations

Layout

The layout file (layout/theme.liquid) wraps every page. It contains the <html>, <head>, and <body> tags plus the {{ content_for_header }} and {{ content_for_layout }} tags:

<!DOCTYPE html>
<html lang="{{ request.locale.iso_code }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ page_title }} — {{ shop.name }}</title>
{{ content_for_header }}
{{ "theme.css" | asset_url | stylesheet_tag }}
</head>
<body>
{% sections "header-group" %}

<main id="MainContent" role="main">
{{ content_for_layout }}
</main>

{% sections "footer-group" %}

<script src="{{ 'theme.js' | asset_url }}" defer></script>
</body>
</html>
content_for_header Is Required

The {{ content_for_header }} tag injects Shopify's required scripts -- analytics, App Bridge, checkout integration, and more. Never remove this tag or your theme will break.

JSON Templates

In Online Store 2.0, templates are JSON files that define which sections appear and in what order. This is what makes the theme editor work:

{
"name": "Product",
"sections": {
"main": {
"type": "product-info",
"settings": {
"show_vendor": true,
"show_sku": false
},
"blocks": {
"title": {
"type": "title",
"settings": {}
},
"price": {
"type": "price",
"settings": {
"show_compare_at": true
}
},
"variant_picker": {
"type": "variant_picker",
"settings": {
"picker_type": "button"
}
},
"buy_buttons": {
"type": "buy_buttons",
"settings": {
"show_dynamic_checkout": true
}
},
"description": {
"type": "description",
"settings": {}
}
},
"block_order": ["title", "price", "variant_picker", "buy_buttons", "description"]
},
"recommendations": {
"type": "related-products",
"settings": {
"heading": "You may also like",
"products_to_show": 4
}
}
},
"order": ["main", "recommendations"]
}

Sections and Section Schemas

Sections are the core building blocks of OS 2.0 themes. Each section is a self-contained Liquid file with its own markup, styles, settings, and blocks.

{% comment %} sections/featured-collection.liquid {% endcomment %}

<div class="featured-collection" style="padding: {{ section.settings.padding }}px 0;">
<div class="page-width">
{% if section.settings.heading != blank %}
<h2 class="section-heading">{{ section.settings.heading }}</h2>
{% endif %}

{% assign collection = collections[section.settings.collection] %}

<div class="product-grid" style="
display: grid;
grid-template-columns: repeat({{ section.settings.columns }}, 1fr);
gap: 24px;
">
{% for product in collection.products limit: section.settings.products_to_show %}
{% render 'product-card', product: product %}
{% endfor %}
</div>
</div>
</div>

{% schema %}
{
"name": "Featured Collection",
"tag": "section",
"class": "featured-collection-section",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Featured Collection"
},
{
"type": "collection",
"id": "collection",
"label": "Collection"
},
{
"type": "range",
"id": "products_to_show",
"min": 2,
"max": 12,
"step": 1,
"default": 4,
"label": "Products to show"
},
{
"type": "range",
"id": "columns",
"min": 2,
"max": 5,
"step": 1,
"default": 4,
"label": "Columns"
},
{
"type": "range",
"id": "padding",
"min": 0,
"max": 100,
"step": 4,
"default": 40,
"unit": "px",
"label": "Section padding"
}
],
"presets": [
{
"name": "Featured Collection"
}
]
}
{% endschema %}

The {% schema %} block at the bottom defines:

  • settings: Configuration options that appear in the theme editor
  • blocks: Repeatable sub-components within the section
  • presets: Default configurations that appear when adding the section
  • templates: Which page types this section can be used on
Schema Settings Types

Shopify supports many setting types: text, textarea, richtext, image_picker, url, video, color, font_picker, collection, product, blog, page, link_list, range, select, checkbox, radio, number, html, and liquid. Each renders a specific input in the theme editor.

Metafields in Liquid

Metafields let you attach custom data to Shopify resources. In Liquid, you access them through the metafields property:

<!-- Access a product metafield -->
{{ product.metafields.custom.warranty_years.value }}

<!-- Use metafield in conditional logic -->
{% if product.metafields.custom.is_featured.value == true %}
<span class="badge badge--featured">Featured</span>
{% endif %}

<!-- Render a metafield image -->
{% assign hero_image = product.metafields.custom.hero_image.value %}
{% if hero_image %}
{{ hero_image | image_url: width: 1200 | image_tag: class: 'hero-image' }}
{% endif %}

<!-- Iterate over a list metafield -->
{% assign ingredients = product.metafields.custom.ingredients.value %}
{% if ingredients %}
<ul class="ingredient-list">
{% for ingredient in ingredients %}
<li>{{ ingredient }}</li>
{% endfor %}
</ul>
{% endif %}

To make metafields available in the theme editor, add them to your section schema:

{
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading"
}
],
"blocks": [
{
"type": "metafield",
"name": "Custom Metafield",
"settings": [
{
"type": "text",
"id": "metafield_key",
"label": "Metafield key",
"info": "Format: namespace.key"
}
]
}
]
}

Performance Optimization

Theme performance directly impacts conversion rates. Shopify provides tools and best practices for keeping themes fast.

Key Principles

  1. Minimize Liquid loops: Every iteration is server-side processing time. Use limit on for loops.
  2. Lazy load images: Use the loading: 'lazy' parameter on image_tag.
  3. Preload critical assets: Use <link rel="preload"> for above-the-fold images and fonts.
  4. Defer JavaScript: Use defer or async on script tags.
  5. Reduce section nesting: Deeply nested sections increase render time.
<!-- Good: Lazy load below-the-fold images -->
{{ product.featured_image | image_url: width: 600 | image_tag:
loading: 'lazy',
widths: '200,400,600,800',
sizes: '(max-width: 600px) 100vw, 50vw'
}}

<!-- Good: Preload above-the-fold hero image -->
<link
rel="preload"
as="image"
href="{{ section.settings.hero_image | image_url: width: 1400 }}"
media="(min-width: 750px)"
>

Theme Inspector

Shopify's Theme Inspector is a Chrome DevTools extension that profiles Liquid rendering time:

  1. Install from the Chrome Web Store
  2. Open your storefront and open DevTools
  3. Navigate to the Shopify tab
  4. See a flame graph of Liquid rendering

The inspector shows which sections, snippets, and Liquid operations consume the most server time. Focus your optimization efforts on the hottest code paths.

Avoid N+1 Queries in Liquid

A common performance mistake is accessing metafields or related resources inside a loop without limits. Each access may trigger a database query. Shopify mitigates this with caching, but keeping loops tight and using limit is still important.

<!-- Bad: Unbounded loop with metafield access -->
{% for product in collection.products %}
{{ product.metafields.custom.badge.value }}
{% endfor %}

<!-- Better: Limit the loop -->
{% for product in collection.products limit: 12 %}
{{ product.metafields.custom.badge.value }}
{% endfor %}

Key Takeaways

  • Liquid has three constructs: objects (output data), tags (logic), and filters (transform output)
  • OS 2.0 themes use JSON templates that reference sections with schema definitions
  • Sections are the primary building block -- self-contained components with settings and blocks
  • Metafields extend the data model and are accessible in Liquid and the theme editor
  • Performance optimization focuses on limiting loops, lazy loading images, and deferring JavaScript
  • The Theme Inspector Chrome extension is essential for profiling render times

Next, we explore Hydrogen -- Shopify's React framework for headless storefronts.