Liquid templating guide
Catalogue lays out your range with Liquid, a
safe, readable templating language. Templates live in your project’s templates/
folder and are compiled at build time — there is no scripting on the device.
This guide covers the syntax, the objects and filters Catalogue provides, and a set of ready-made recipes you can paste straight into a template.
Syntax
Liquid has two kinds of markup:
- Output —
{{ ... }}prints a value. - Tags —
{% ... %}control logic and flow, and print nothing themselves.
{{ product.title }} {# outputs the title #}
{% if product.in_stock %}…{% endif %} {# logic, prints nothing itself #}
{# this is a comment #}Anything outside {{ }} and {% %} is passed through unchanged.
Variables
Create variables with assign, or capture a block of markup with capture:
{% assign heading = product.title | upcase %}
{{ heading }}
{% capture badge %}
{{ product.category }} · {{ product.sku }}
{% endcapture %}
{{ badge }}Objects
Which objects are available depends on the template being rendered.
| Object | Available in | Description |
|---|---|---|
product | product.liquid | The current product (see fields below). |
products | index.liquid, category templates | Array of all products in scope. |
categories | any template | Array of category objects, each with title and products. |
catalogue | any template | Catalogue-wide values: name, currency, currency_symbol. |
The product object
Every field from your imported data
is available on product:
{{ product.id }}
{{ product.title }}
{{ product.description }}
{{ product.price }}
{{ product.in_stock }} {# true or false #}
{{ product.stock }}
{{ product.category }}
{# arrays #}
{{ product.tags | join: ", " }}
{{ product.images | first }}
{# attributes map #}
{% for attribute in product.attributes %}
{{ attribute[0] }}: {{ attribute[1] }}
{% endfor %}
{# variants #}
{% for variant in product.variants %}
{{ variant.title }} — {{ variant.price | money }}
{% endfor %}Filters
Filters transform a value. They are applied with |, and can be chained; each
filter receives the result of the one before it.
{{ product.title | upcase }}
{{ product.description | truncate: 120 }}
{{ product.title | append: " (" | append: product.sku | append: ")" }}Standard filters
Catalogue supports the standard Liquid filters, including:
- Text —
upcase,downcase,capitalize,truncate,truncatewords,strip,replace,append,prepend,remove. - Math —
plus,minus,times,divided_by,round,ceil,floor,abs. - Arrays —
size,first,last,join,sort,sort_natural,map,where,uniq,reverse. - Defaults —
defaultsupplies a fallback for an empty value.
{{ product.description | default: "No description provided." }}
{{ products | where: "category", "Stationery" | size }} productsCatalogue filters
Two extra filters are specific to Catalogue:
| Filter | Example | Result |
|---|---|---|
money | {{ 8.5 | money }} | £8.50 — formats a number as a price using the catalogue currency. |
image_url | {{ product.image | image_url }} | Resolves an image filename to its path in the built package. |
<img src="{{ product.image | image_url }}" alt="{{ product.title }}">
<p class="price">{{ product.price | money }}</p>Control flow
Conditionals
{% if product.stock > 20 %}
In stock
{% elsif product.stock > 0 %}
Low stock — {{ product.stock }} left
{% else %}
Out of stock
{% endif %}
{% unless product.in_stock %}
<span class="badge">Unavailable</span>
{% endunless %}Operators: ==, !=, >, <, >=, <=, and, or, contains.
{% if product.tags contains "field" and product.in_stock %}…{% endif %}Case / when
{% case product.category %}
{% when "Stationery" %}
<span class="icon">✎</span>
{% when "Office" %}
<span class="icon">▤</span>
{% else %}
<span class="icon">•</span>
{% endcase %}Loops
{% for product in products %}
{{ product.title }}
{% else %}
No products to show.
{% endfor %}Loops accept limit and offset, and expose the forloop object:
{% for product in products limit: 6 %}
{{ forloop.index }} of {{ forloop.length }}: {{ product.title }}
{% endfor %}Use break and continue to control iteration.
Recipes
Copy these into a template and adapt the class names to your styles.
Product grid
A responsive grid of products, each linking to its own page, with image, title and price.
<ul class="grid">
{% for product in products %}
<li class="grid__item">
<a href="{{ product.id }}.html">
{% if product.image %}
<img src="{{ product.image | image_url }}" alt="{{ product.title }}">
{% endif %}
<h3>{{ product.title }}</h3>
<p class="price">{{ product.price | money }}</p>
</a>
</li>
{% endfor %}
</ul>Price formatting
Use the money filter for a currency-aware price. To show a variant price range,
sort the variant prices and format the lowest and highest:
{% if product.variants and product.variants.size > 0 %}
{% assign prices = product.variants | map: "price" | sort %}
{% assign low = prices | first %}
{% assign high = prices | last %}
{% if low == high %}
{{ low | money }}
{% else %}
{{ low | money }} – {{ high | money }}
{% endif %}
{% else %}
{{ product.price | money }}
{% endif %}Conditional stock display
Show a clear, graded stock status from the stock and in_stock fields:
{% if product.in_stock == false %}
<p class="stock stock--out">Out of stock</p>
{% elsif product.stock and product.stock <= 10 %}
<p class="stock stock--low">Only {{ product.stock }} left</p>
{% else %}
<p class="stock stock--in">In stock</p>
{% endif %}Grouping by category
Iterate the categories object to render each category with its products under a
heading:
{% for category in categories %}
<section class="category">
<h2>{{ category.title }}</h2>
<ul>
{% for product in category.products %}
<li>{{ product.title }} — {{ product.price | money }}</li>
{% endfor %}
</ul>
</section>
{% endfor %}If you only have a flat products array, group it on the fly. Collect the distinct
categories first, then filter the products for each:
{% assign categories = products | map: "category" | uniq | sort %}
{% for category in categories %}
<h2>{{ category | default: "Uncategorised" }}</h2>
{% assign group = products | where: "category", category %}
<ul>
{% for product in group %}
<li>{{ product.title }}</li>
{% endfor %}
</ul>
{% endfor %}Where to go next
- Missing a field in a template? Check the schema reference.
- Template not rendering as expected? See Troubleshooting.