Skip to content
Published Authored byBilly Reiner

Glossary · Defined term

Shopify Liquid: definition and cheat sheet

Liquid is "a template language created by Shopify", open source on GitHub and used well beyond Shopify1. Shopify co-founder and CEO Tobias Lütke developed it4. The version your theme runs "extends the open-source version of Liquid" with the tags, filters and objects a store needs1.

The first part of this page defines Liquid. The rest is a cheat sheet of the tags, filters and objects that show up in almost every theme, each one checked against shopify.dev on 16 September 2026, along with the documented limits that break themes in production.

Definition

Liquid is the template language Shopify created and every Shopify theme is written in. It mixes HTML with three building blocks: objects that output store data, tags that add logic, and filters that change output. Liquid is open source, and Shopify themes run an extended version of it.

The GitHub project calls it a "safe, customer facing template language for flexible web apps"3, and the README says it had to be "non evaling and secure". That design choice is why Shopify can let merchants and developers edit templates on its own servers: a Liquid template can read store data and loop over it, but it can't run arbitrary code.

Shopify runs slightly different Liquid variants in notification templates, Shopify Flow, and order printer and packing slip templates. The theme reference, and this cheat sheet, cover theme Liquid only1.

How Liquid works

Output goes in double curly braces, logic goes in curly-brace-percent tags, and filters follow a pipe character. A hyphen inside either delimiter strips the whitespace Liquid would otherwise leave in the HTML.

Liquid Objects, tags and filters in four lines
{{ product.title }}
{{ product.title | upcase }}
{% if product.available %}In stock{% endif %}
{%- assign title = product.title | escape -%}

Liquid has six data types: string, number, boolean, nil, array and empty. The comparison operators are ==, !=, >, <, >= and <=, and the logical ones are or, and and contains2.

Two rules surprise people coming from other languages. Only nil and false are falsy, so an empty string counts as true. And when a tag has more than one operator, Liquid evaluates them right to left, with no parentheses to change the order2.

Liquid cheat sheet: tags

Tags hold the logic. These are the ones a Shopify theme uses constantly, with the syntax shopify.dev documents.

TagSyntaxWhat it does
if / elsif / else{% if cond %}…{% elsif other %}…{% else %}…{% endif %}Conditional output
unless{% unless cond %}…{% endunless %}Renders unless the condition is true. Accepts elsif too
case / when{% case x %}{% when 'a' %}…{% else %}…{% endcase %}Switch on a value
for{% for item in array limit: 4 %}…{% endfor %}Loops an array or a range (1..5). Also offset and reversed. Stops at 50 iterations5
paginate{% paginate collection.products by 24 %}…{% endpaginate %}Splits an array across pages, 1 to 250 items per page6
assign{% assign name = value %}Creates a variable
capture{% capture name %}…{% endcapture %}Creates a string variable from a block of output
render{% render 'snippet' with product as item %}Renders a snippet or app block in its own variable scope7
section{% section 'header' %}Renders a section statically
sections{% sections 'header-group' %}Renders a section group
content_for{% content_for 'blocks' %}Where theme blocks render. A single static block needs type and id
liquid{% liquid … %}Many tags inside one delimiter, one tag per line. Output with echo (example below)
comment{% comment %}…{% endcomment %} or {% # note %}Code that is parsed but never rendered
doc{% doc %} @param {string} title {% enddoc %}LiquidDoc annotations for tooling. Not rendered
form{% form 'localization' %}…{% endform %}Outputs a Shopify form, such as the country and language selector

The liquid tag is the one that most cleans up a messy snippet. Shopify's own example:

Liquid The liquid tag, from shopify.dev
{% liquid
  assign product_type = product.type | downcase
  assign message = ''

  case product_type
    when 'health'
      assign message = 'This is a health potion!'
    when 'love'
      assign message = 'This is a love potion!'
    else
      assign message = 'This is a potion!'
  endcase

  echo message
%}

Liquid cheat sheet: filters

Filters change what an object outputs. They chain left to right after a pipe, and several take named parameters after a colon.

FilterExampleWhat it does
money family{{ product.price | money }}Formats a price with the store's currency format. Also money_with_currency, money_without_currency, money_without_trailing_zeros
image_url{{ product | image_url: width: 800 }}CDN URL for an image. Needs width or height, max 5760 px, never upscales9
image_tag{{ product | image_url: width: 800 | image_tag }}Full <img> with width, height, srcset, and lazy loading below the fold
asset_url{{ 'cart.js' | asset_url }}CDN URL for a file in the theme's assets folder
t{{ 'products.add_to_cart' | t }}Translated string from the theme's locale files
json{{ product | json }}JSON output, quotes included10
default{{ variant.url | default: product.url }}Fallback when a value is empty, false or nil. allow_false: true keeps false
escape{{ product.title | escape }}HTML-escapes <, >, quotes and &
handleize{{ product.title | handleize }}Turns "Health potion" into health-potion. Alias handle
date{{ article.created_at | date: '%B %d, %Y' }}strftime formatting. 'now' gives the render time, format: 'abbreviated_date' a locale-aware one
where{{ collection.products | where: 'available' }}Keeps array items whose property matches, or is true

For images, image_url plus image_tag is the pair to use: it's the route through which Shopify serves modern formats, covered in AVIF and WebP on Shopify. The old img_url filter is deprecated9.

Liquid cheat sheet: objects

Objects hold the store's data. Some are global, some only exist on the template that owns them, like product on a product page.

ObjectCommon propertiesNotes
producttitle, price, compare_at_price, variants, availablePrices are in the currency's subunit, in the shopper's local currency11
collectionproductsPaginate collection.products to get past 50 items
carttotal_price, item_count, items, currencytotal_price is after discounts, in subunits
shopname, currency, money_format, enabled_currenciesshop.currency is the base currency, not the shopper's
settingsAny theme setting, e.g. settings.currency_code_enableValues from the theme editor, defined in config/
routesroot_url, cart_url, search_url, collections_urlLocale-aware URLs. Use them instead of hard-coding /cart
localizationcountry, language, available_countriesWhat the country and language selector reads
forloopindex, index0, first, last, lengthOnly inside a for loop5
content_for_header(none)Shopify's required scripts. Goes in <head>. Don't parse or modify it
content_for_layout(none)The current template's content. Goes in <body> of theme.liquid

Liquid limits and gotchas

Most Liquid bugs on Shopify trace back to a short list of documented limits. A for loop stops at 50 items, snippets can't see outside variables, and prices are integers in cents.

  • A for loop runs at most 50 times. For more, wrap it in paginate, which allows 1 to 250 items per page and stops at the 25,000th item56. Paginated collections are where Shopify's ?page=N collection URLs come from.
  • render isolates variables in both directions. A snippet can't read a variable you assigned in the section that calls it, so pass it in with with or for7.
  • include is deprecated because of how it handles variables, and you can't use it inside a snippet that was called with render7.
  • image_url returns an error if you give it neither a width nor a height9.
  • Prices are integers in the currency's subunit: 1000 is $10.00, and 1000 yen arrives as 100000. Format them with the money filters rather than dividing by hand11.
  • An empty string is truthy. After {% assign note = '' %}, the test {% if note %} still passes. Compare against empty when a value can be blank.
  • assign can overwrite a global object if you reuse its name. Don't call a variable product or cart.
  • The json filter leaves out a variant's inventory_quantity and inventory_policy on stores created after 5 December 201710, so a script that expects stock counts in {{ product | json }} gets nothing.

Where Liquid lives in a Shopify theme

Only one file is required: layout/theme.liquid. Everything else in a theme sits in eight folders: assets, blocks, config, layout, locales, sections, snippets and templates.

Templates can be JSON or Liquid. A JSON template only wraps sections, while a Liquid template contains code12. In a theme built on JSON templates, the Liquid itself sits in sections, blocks and snippets. robots.txt.liquid is the notable exception: it has to stay a Liquid template.

For SEO, the Liquid you touch most sits in the <head> of theme.liquid: meta robots rules (see noindex in theme.liquid) and site-wide JSON-LD (see custom JSON-LD via theme.liquid). Liquid renders on Shopify's servers, so everything it outputs is in the HTML crawlers receive, including crawlers that don't run JavaScript.

Liquid is the layer where most Shopify SEO fixes are actually written.

  • Currency format: the store setting the money filters read.
  • robots.txt.liquid: the Liquid template behind /robots.txt.
  • JSON-LD: the structured-data syntax most often written in Liquid.
  • Shopify API key: the app side of Shopify, which needs credentials that theme code never does.