Liquid is Shopify's template language: {{ }} outputs a value, {% %} runs logic, and filters chain onto output with |. Everything else is knowing which objects exist where and which filters do what. This page is the reference, plus the constraints that are not obvious until you hit them.
Verified against Shopify's own documentation rather than written from memory, because a cheat sheet with one wrong filter name is worse than no cheat sheet.
Syntax basics
| Syntax | What it does |
|---|---|
{{ product.title }} |
Output a value |
{{- product.title -}} |
Output, trimming surrounding whitespace |
{% if condition %} |
Logic tag |
{%- if condition -%} |
Logic tag, trimming whitespace |
{{ price | money }} |
Apply a filter |
{{ a | plus: 1 | times: 2 }} |
Chain filters left to right |
{% assign x = 'value' %} |
Set a variable |
{% capture x %}...{% endcapture %} |
Set a variable from a block of output |
{% comment %}...{% endcomment %} |
Comment |
The gotchas that cost you an afternoon
These are the ones that make experienced developers stare at working-looking code.
- No parentheses in conditions. You cannot write
{% if (a and b) or c %}. Nest theifstatements instead. - No ternary operator. There is no
a ? b : c. It is always an{% if %}block, ordefaultif you only need a fallback value. containsworks on strings, not objects.{% if collection.products contains product %}does not do what it looks like. It works for strings and arrays of strings only.forloops stop at 50 iterations. This is a hard default. Anything larger needs{% paginate %}, and forgetting it is why a collection page silently shows 50 products.renderhas isolated scope. A snippet cannot see the variables around it. Pass everything as a parameter:{% render 'card', product: product %}.- Liquid does not run inside
{% stylesheet %}or{% javascript %}. Pass values through CSS custom properties on the element instead. img_tagandimg_urlare deprecated. Useimage_tagandimage_url.
Tags worth knowing
| Tag | Use |
|---|---|
{% liquid %} |
Multiple statements without repeating delimiters |
{% render 'name', var: value %} |
Render a snippet, isolated scope |
{% render 'tag' for product.tags as tag %} |
Render a snippet per item |
{% content_for 'blocks' %} |
Render the theme blocks a merchant added |
{% content_for 'block', type: 'slide', id: 'slide-1' %} |
Render one static block |
{% section 'name' %} / {% sections 'group' %} |
Render a section or section group |
{% paginate collection.products by 12 %} |
Required past 50 items |
{% form 'product', product %} |
Form with the right action and hidden fields |
{% schema %} |
JSON settings a section or block exposes to the theme editor |
{% stylesheet %} / {% javascript %} |
Per-component CSS and JS (sections, blocks, snippets only) |
{% style %} |
CSS that live-updates in the editor for colour settings |
{% doc %} |
LiquidDoc header, required on snippets and static blocks |
{% layout 'name' %} |
Choose a layout |
Form types accepted by {% form %}: product, contact, customer_login, create_customer, customer_address, cart, localization, new_comment, recover_customer_password, reset_customer_password, activate_customer_password, guest_login, currency, customer, storefront_password.
Inside a for loop you get forloop.index, forloop.index0, forloop.first, forloop.last, forloop.length.
Shopify Liquid filters, by category
| Category | Filters |
|---|---|
| Money | money, money_with_currency, money_without_currency, money_without_trailing_zeros |
| Images | image_url, image_tag, placeholder_svg_tag, preload_tag |
| Array | where, map, reject, find, find_index, has, first, last, sort, sort_natural, reverse, size, join, uniq, compact, concat, sum |
| String | split, append, prepend, remove, replace, strip, truncate, upcase, downcase, capitalize, escape, handleize, url_encode, url_decode, camelize, slice, strip_html, newline_to_br, pluralize |
| Math | plus, minus, times, divided_by, modulo, round, ceil, floor, abs, at_least, at_most |
| Colour | color_to_hex, color_to_hsl, color_to_rgb, color_to_oklch, color_darken, color_lighten, color_mix, color_modify, color_saturate, color_brightness |
| HTML | link_to, script_tag, stylesheet_tag, time_tag, inline_asset_content |
| Hosted files | asset_url, file_url, global_asset_url, shopify_asset_url |
| Format | date, json, structured_data, metafield_tag, metafield_text |
| Other | t (translation), default, default_errors, default_pagination, font_face, font_url, payment_button |
The four that come up constantly:
{{ product.price | money }}
{{ product.featured_image | image_url: width: 800 | image_tag: class: 'card__image' }}
{{ collection.products | where: 'available', true | size }}
{{ 'products.product.add_to_cart' | t }}
Objects available everywhere
shop, settings, cart, customer, request, routes, template, theme, localization, collections, pages, all_products, articles, blogs, images, linklists, metaobjects, canonical_url, page_title, page_description, handle.
Page-specific objects (product, collection, article, blog, order, search) exist only in their own templates. That is the most common cause of a value rendering blank: the object is not in scope on that template.
Theme file structure
| Folder | Contents |
|---|---|
layout/ |
theme.liquid, the HTML shell. Must contain {{ content_for_header }} and {{ content_for_layout }} |
templates/ |
JSON (or Liquid) files defining which sections render per page type |
sections/ |
Full-width modules with a {% schema %} |
blocks/ |
Smaller reusable components, also with a schema, nestable |
snippets/ |
Fragments rendered with {% render %}, with a {% doc %} header |
assets/ |
CSS, JS, images, fonts |
config/ |
settings_schema.json and settings_data.json |
locales/ |
Translations, referenced as `{{ 'key' |
Is checkout.liquid still usable?
No, and the last piece of it sunset in August 2026. This matters because the dates arrived recently and a lot of advice online predates them.
checkout.liquidis unsupported for the Information, Shipping and Payment steps.- It sunset for the Thank you and Order status pages on 28 August 2025.
- Script tags on those pages sunset for Plus stores on 28 August 2025, and for non-Plus stores on 26 August 2026.
Stores still relying on it must move to Shopify Extensions in Checkout. Shopify's admin carries a report listing your existing checkout customizations and how they map across. The details are in their upgrade documentation.
Note that checkout.liquid was only ever available to Shopify Plus merchants. On a non-Plus store, checkout customization has always been the theme editor and apps.
Writing Liquid without writing Liquid
Everything above is the reference for editing by hand, which is still the right approach for anything unusual. For the routine work, a section variant, a bundle block, a restyled product page, describing it and generating the Liquid produces the same file with a proper schema in less time than looking up the filter takes. The output is standard theme code, so this cheat sheet still applies to reading and editing it afterwards.
FAQ
What is Liquid in Shopify?
Liquid is the open-source template language Shopify uses to render storefronts. It runs server-side on Shopify's infrastructure, outputs values with {{ }} and runs logic with {% %}. It has no access to a database or server-side routes of your own; it renders the data Shopify hands it.
What are Shopify Liquid filters?
Filters transform output and chain with |. They cover money formatting, image URLs and tags, array operations, string manipulation, maths, colour, HTML helpers and translations. The full list by category is in the table above.
How do I output JSON in Liquid?
Use the json filter: {{ product | json }} serializes an object, and it is the standard way to hand data to JavaScript. Note that theme templates and section settings are also JSON files in their own right, which is a separate thing from the filter.
Why is my Liquid loop only showing 50 items?
Because for loops cap at 50 iterations by default. Wrap the loop in {% paginate collection.products by 12 %} and render {{ paginate | default_pagination }} for the controls.
Why can my snippet not see my variable?
{% render %} creates an isolated scope by design. Pass the variable explicitly as a parameter: {% render 'card', product: product %}.
Does Liquid have an if/else shorthand?
No ternary operator and no parentheses in conditions. Use {% if %} blocks, nested where the logic needs grouping, or the default filter when you only need a fallback value.
Keep reading: what custom Liquid is and where it lives and how Shopify theme development actually works.