← All posts Shopify Liquid: The Definitive Reference for Merchants and Developers

Shopify Liquid: The Definitive Reference for Merchants and Developers

Master Shopify Liquid with this definitive reference: objects, filters, tags, section schemas, metafields, and the 2026 platform changes every store owner

Shopify Liquid is the open-source templating language that powers every Shopify storefront. It connects your store's backend data (products, collections, cart, customer) to the HTML your shoppers see, using three primitives: objects, tags, and filters. Getting those three things right is the difference between a theme that converts and one that costs you money every month in app subscriptions and developer tickets.

Key takeaways

  • Liquid's three primitives are objects {{ }}, tags {% %}, and filters |. Every customization you do on a Shopify theme flows through those three.
  • The {% render %} tag replaced {% include %} in 2019 and is the only inclusion tag Online Store 2.0 supports. Swapping the two is still the single fastest performance win on legacy themes.
  • New filters added in 2025-2026 include image_tag with fetchpriority and sizes attributes, and metafield_tag for structured metafield rendering.
  • One unoptimized collection loop with nested tag checks can add 2-4 seconds to LCP and costs roughly 7% of mobile revenue per second slower.
  • Replacing 3-5 mid-tier apps with native Liquid sections typically saves $100-$300 a month and removes 150-600 KB of third-party JavaScript per page.

What is Shopify Liquid?

Liquid is a template language that lets you create a single template to host static content and dynamically insert information depending on what is being rendered. A product template, for example, holds all your standard product attributes and dynamically swaps in the right content for each product URL.

Shopify's flavour of Liquid extends the open-source version with store-specific objects (product, collection, cart, customer, shop) and filters built for commerce (money, image_url, metafield_tag). As of January 13, 2026, Shopify also updated how it processes Liquid code internally to make theme rendering more reliable, with no action required from merchants.

The three primitives

1. Objects

Objects output dynamic data using double curly braces:

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

Key global objects you will use on every theme build:

ObjectWhat it exposes
productTitle, price, variants, images, metafields
collectionProducts, filters, pagination
cartLine items, total price, item count
customerName, email, order history, tags
shopName, currency, locale, metafields
requestCurrent page type, locale, host

Objects can represent a single value or expose multiple properties. The request object is particularly useful for conditional rendering based on page type.

2. Tags

Tags add logic and flow control using {% %} syntax. The categories you need to know:

Control flow liquid {% if product.available %} <button>Add to cart</button> {% else %} <button disabled>Sold out</button> {% endif %}

Iteration liquid {% for product in collection.products limit: 24 %} {% render 'product-card', product: product %} {% endfor %}

Variable assignment liquid {% assign sale_price = product.compare_at_price | minus: product.price %} {% capture button_label %}Add {{ product.title }}{% endcapture %}

Theme tags (sections, snippets, layouts) liquid {% render 'product-card', product: product, show_vendor: true %} {% section 'announcement-bar' %} {% layout 'checkout' %}

3. Filters

Filters modify output using the pipe character |. Multiple filters chain left to right:

{{ product.title | upcase | truncate: 50 }}
{{ product.price | money }}
{{ product.featured_image | image_url: width: 800 | image_tag: loading: 'lazy', widths: '200,400,600,800' }}

Critical filter categories:

  • Money filters: | money, | money_with_currency, | money_without_trailing_zeros. Never hardcode currency symbols. The | money filter respects your store's currency, locale, and formatting settings. A hardcoded $ symbol breaks the moment you enable multi-currency in Shopify Markets.
  • Image filters: | image_url: width: X followed by | image_tag. The modern image_url and image_tag filters replaced the older img_url and img_tag and deliver responsive images with proper srcset, sizes, lazy loading, and fetchpriority attributes in a single chain.
  • Array filters: | where, | map, | sort, | uniq, | join, | concat. Use | where to filter arrays without loops.
  • String filters: | upcase, | downcase, | truncate, | strip_html, | escape, | split.
  • Math filters: | plus, | minus, | times, | divided_by, | ceil, | floor, | round.
  • Color filters: | color_lighten, | color_darken, | color_mix, | color_contrast. The color_contrast filter returns the contrast ratio between two colors, which is essential for accessible badge and button design.
  • Localization filters: | t (translation), | format_address, | currency_selector.

A note on the | where gotcha: the filter is case-sensitive. | where: 'tags', 'Sale' will not match a tag of 'sale'. Use | downcase before comparison.

Sections and the {% schema %} tag

Online Store 2.0 made sections available everywhere, not just the homepage. Every section you build should have a full {% schema %} block so merchants can edit content from the theme editor without opening a code file.

Each section can have only a single {% schema %} tag, which must contain valid JSON. Having more than one, or nesting it inside another Liquid tag, produces a syntax error.

A minimal but complete section schema:

{% schema %}
{
  "name": "Feature banner",
  "tag": "section",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Our promise"
    },
    {
      "type": "image_picker",
      "id": "image",
      "label": "Background image"
    }
  ],
  "blocks": [
    {
      "type": "feature",
      "name": "Feature item",
      "settings": [
        { "type": "text", "id": "feature_text", "label": "Feature", "default": "Free shipping" }
      ]
    }
  ],
  "max_blocks": 4,
  "presets": [{ "name": "Feature banner" }]
}
{% endschema %}

Critical rule: treat setting id values like database column names. They become persistent keys in template JSON across every store that installs your section. Renaming an id orphans existing values. Changing a text setting to image_picker does not migrate data. Prefix when sections grow large: use hero_heading rather than heading if you also have card_heading in blocks.

Sections with presets should not be statically rendered. Statically rendered sections cannot be removed or reordered by merchants.

The {% render %} tag (and why {% include %} is dead)

The {% render %} tag is the only way to include snippets in modern Shopify themes. {% include %} shared variable scope across snippets, which caused naming conflicts and made performance profiling unreliable. {% render %} creates an isolated scope: snippets cannot read parent variables unless they are passed in as named parameters.

{# DEPRECATED - shares parent scope #}
{% include 'product-card' %}

{# CORRECT - isolated scope, explicit inputs #}
{% render 'product-card', product: product, show_vendor: true %}

{# Loop form #}
{% render 'product-card' for collection.products as product %}

Isolated scope means snippets are safely reusable across product detail pages, search results, AJAX cart responses, and Section Rendering API requests with zero rewrites. Replacing {% include %} with {% render %} also clears every Theme Check error that blocks Shopify Theme Store submission.

Metafields in Liquid

Metafields let you extend Shopify's data model with custom data (size charts, ingredients, care instructions, specifications) without apps. The most powerful metafield types for Liquid development are:

  • JSON for structured data (render with | metafield_tag or parse the value)
  • Rich text for merchant-editable HTML body copy
  • File reference for images and videos attached to products
  • Product/collection reference for explicit relationships between resources

Access pattern:

{% assign size_chart = product.metafields.custom.size_chart %}
{% if size_chart != blank %}
  {{ size_chart.value | metafield_tag }}
{% endif %}

The != blank guard is non-negotiable. Skip it and products without a size chart render an empty <table> that crashes layout on mobile Safari.

Always create metafield definitions through Settings > Custom data in the Shopify admin, never on-the-fly via the API. Definitions give you type validation, merchant-friendly labels, and admin visibility. Third-party apps like Yotpo and Klaviyo read these definitions, so a clean schema feeds the rest of your stack.

Performance patterns that actually matter

Loop optimization

Nested loops are the most common performance killer in the Liquid themes I audit. An outer loop over 50 products combined with an inner loop over 20 tags per product runs 1,000 iterations per page render. Use the contains operator instead:

{# BAD: O(n*m) - 50 products x 20 tags = 1,000 iterations #}
{% for product in collection.products %}
  {% for tag in product.tags %}
    {% if tag == 'featured' %}...{% endif %}
  {% endfor %}
{% endfor %}

{# GOOD: one pass, contains check is O(1) #}
{% for product in collection.products limit: 24 %}
  {% if product.tags contains 'featured' %}...{% endif %}
{% endfor %}

Always add limit: to collection loops. One unoptimized loop can add 2-4 seconds to LCP on mobile.

Image rendering

{# BAD: no dimensions, no srcset, browser cannot reserve space (CLS) #}
<img src="{{ product.featured_image | img_url: 'master' }}">

{# GOOD: srcset, lazy loading, explicit width hint #}
{{ product.featured_image
  | image_url: width: 800
  | image_tag:
      loading: 'lazy',
      widths: '200,400,600,800',
      sizes: '(min-width: 768px) 400px, 100vw',
      alt: product.title }}

The image_tag filter does in one line what previously required three lines of HTML with manual srcset construction.

Whitespace control

Liquid outputs whitespace around every tag. On large templates this bloats HTML and slows TTFB. Use the - modifier to strip it:

{%- for product in collection.products -%}
  {{- product.title -}}
{%- endfor -%}

Section Rendering API

The Section Rendering API allows sections to be rendered independently via AJAX, enabling faster page transitions and dynamic cart updates without full page reloads. This is the foundation for app-like experiences within standard Liquid themes, without the overhead of going headless.

Theme Check and tooling

Theme Check 2.0 is now built into the Shopify CLI and catches performance issues, accessibility problems, and deprecated pattern usage before you deploy. Run it on every commit. It flags:

  • {% include %} calls (deprecated)
  • Missing alt attributes on image tags
  • img_url usage (replaced by image_url)
  • Loops without limit:
  • Missing content_for_header in theme.liquid

The Theme Inspector overlay shows Liquid rendering times directly on your storefront, so you can see which sections, snippets, and loops are slow before you optimize.

As of 2026, Shopify's Dev MCP also lets AI agents generate validated Liquid code and scaffold section schemas, which speeds up boilerplate work. Treat AI output as a first draft and always review for Shopify-specific edge cases.

App replacement: what Liquid can do natively

Replacing 3-5 apps per theme with native Liquid sections typically saves $100-$300 a month and removes 150-600 KB of third-party JavaScript. App bloat costs 40-200 ms per third-party script on mobile, which is the exact range that drops Lighthouse mobile scores from 80 to 60.

Functions that Liquid handles natively (no app required):

  • Countdown banners with schema-controlled end dates
  • FAQ accordions with block-per-question architecture
  • Testimonial grids with star ratings and metafield-stored reviews
  • Sticky add-to-cart bars via IntersectionObserver plus a Liquid product section
  • Feature comparison tables using JSON metafields
  • Before/after image sliders with vanilla JS and two image-picker settings

Sections beat apps on three axes: they render before paint (no layout shift), they live in your theme repo (version control is trivial), and they expose schema settings so merchants edit content without a developer.

Liquid vs. Hydrogen: the honest answer

Liquid remains the right choice for the vast majority of Shopify stores because it delivers faster development, lower maintenance costs, and full access to Shopify's CDN and hosting infrastructure. Headless (Hydrogen/Oxygen) makes sense for brands with dedicated frontend engineering teams and complex multi-storefront architectures.

Go headless when you have a dedicated frontend team, three or more storefronts sharing a backend, or an interaction model Liquid genuinely cannot reach. For everyone else, the question is not "Liquid or Hydrogen" but "how do I write Liquid that does not embarrass me in two years?"

If you need help auditing or building a theme to these standards, see the Shopify theme developer services page, or explore Shopify speed optimization if performance is the specific bottleneck.

Quick-reference cheat sheet

Output syntax

{{ product.title }}              {# string output #}
{{ product.price | money }}      {# filtered output #}
{{- product.title -}}            {# whitespace stripped #}

Control flow

{% if %} / {% elsif %} / {% else %} / {% endif %}
{% unless %} / {% endunless %}
{% case product.type %}{% when 'Apparel' %}...{% endcase %}

Loops

{% for item in array limit: 24 offset: 0 %}
  {{ forloop.index }} of {{ forloop.length }}
{% else %}
  No items found.
{% endfor %}

Most-used filters at a glance

| money               {# currency-safe price output #}
| image_url: width: X {# CDN-resized image URL #}
| image_tag           {# full responsive <img> tag #}
| metafield_tag       {# renders metafield value with correct HTML #}
| where: 'key', val   {# filter array without a loop #}
| map: 'property'     {# pluck a property from array of objects #}
| json                {# safe JSON output for script tags #}
| t                   {# translation key lookup #}
| handleize          {# string to URL-safe handle #}
| date: '%B %d, %Y'  {# formatted date output #}

Bookmark the official Liquid reference alongside this post. The official docs cover every object property and filter parameter in full.

shopify liquidtheme developmentshopifyliquid filtersonline store 2.0

Frequently asked questions

What is Shopify Liquid used for?

Shopify Liquid is the templating language that connects your store's data (products, collections, cart, customer) to the HTML your shoppers see. It powers every Shopify theme through three building blocks: objects that output data, tags that add logic and loops, and filters that format output.

Is Liquid still relevant in 2026 or should I go headless?

Liquid remains the right choice for the vast majority of Shopify stores. It delivers faster development, lower maintenance costs, and full access to Shopify's CDN. Headless Hydrogen only makes sense for brands with dedicated frontend engineering teams and complex multi-storefront needs.

What is the difference between the render tag and the include tag in Shopify Liquid?

The render tag creates an isolated variable scope, so snippets cannot accidentally read or modify variables from the parent template. The include tag shared scope, which caused naming conflicts and made performance profiling unreliable. Shopify deprecated include and render is the only inclusion tag supported by Online Store 2.0.