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.
Liquid
{{ 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:

Liquid
{% 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.

ObjectAvailable inDescription
productproduct.liquidThe current product (see fields below).
productsindex.liquid, category templatesArray of all products in scope.
categoriesany templateArray of category objects, each with title and products.
catalogueany templateCatalogue-wide values: name, currency, currency_symbol.

The product object

Every field from your imported data is available on product:

Liquid
{{ 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.

Liquid
{{ product.title | upcase }}
{{ product.description | truncate: 120 }}
{{ product.title | append: " (" | append: product.sku | append: ")" }}

Standard filters

Catalogue supports the standard Liquid filters, including:

  • Textupcase, downcase, capitalize, truncate, truncatewords, strip, replace, append, prepend, remove.
  • Mathplus, minus, times, divided_by, round, ceil, floor, abs.
  • Arrayssize, first, last, join, sort, sort_natural, map, where, uniq, reverse.
  • Defaultsdefault supplies a fallback for an empty value.
Liquid
{{ product.description | default: "No description provided." }}
{{ products | where: "category", "Stationery" | size }} products

Catalogue filters

Two extra filters are specific to Catalogue:

FilterExampleResult
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.
Liquid
<img src="{{ product.image | image_url }}" alt="{{ product.title }}">
<p class="price">{{ product.price | money }}</p>

Control flow

Conditionals

Liquid
{% 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.

Liquid
{% if product.tags contains "field" and product.in_stock %}…{% endif %}

Case / when

Liquid
{% case product.category %}
  {% when "Stationery" %}
    <span class="icon">✎</span>
  {% when "Office" %}
    <span class="icon">▤</span>
  {% else %}
    <span class="icon">•</span>
{% endcase %}

Loops

Liquid
{% for product in products %}
  {{ product.title }}
{% else %}
  No products to show.
{% endfor %}

Loops accept limit and offset, and expose the forloop object:

Liquid
{% 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.

Liquid
<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:

Liquid
{% 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:

Liquid
{% 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:

Liquid
{% 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:

Liquid
{% 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