Clicks & Carts

Shopify Liquid filters: the working reference

Money, images and translations cover most of it. Getting those three right is most of a theme's quality.

7 min read · APIs & data ·

Liquid has a lot of filters and theme work uses perhaps twenty. Three of them — money, images and translations — account for most of a theme's quality, and each has a trap.

Money

``liquid {{ product.price | money }} {% comment %} £24.00 {% endcomment %} {{ product.price | money_without_trailing_zeros }} {{ product.price | money_with_currency }} {% comment %} £24.00 GBP {% endcomment %} {{ product.price | money_without_currency }} ``

The trap: prices are integers in the currency's smallest unit. 24.00 is stored as 2400. Dividing by 100 yourself and formatting the result produces prices that are wrong in currencies with different subdivisions and ignores the store's format settings.

Always use money. In a multi-currency store it's also what applies the correct presentment currency.

Images

```liquid {{ product.featured_image | image_url: width: 800 }} {{ product.featured_image | image_url: width: 800, height: 800, crop: 'center' }}

{{ product.featured_image | image_tag: widths: '400, 600, 800, 1200', sizes: '(min-width: 768px) 50vw, 100vw', loading: 'lazy', alt: product.title }} ```

image_tag generates a complete <img> with srcset, dimensions and modern format negotiation. Using it instead of hand-writing image tags fixes several performance checklist items at once — correct sizing, explicit dimensions, lazy loading.

The trap: loading: 'lazy' on the LCP image. Never lazy-load the hero or first product image; it's the most common self-inflicted LCP regression. Use loading: 'eager' and fetchpriority: 'high' there.

Translations

``liquid {{ 'products.product.add_to_cart' | t }} {{ 'cart.general.item_count' | t: count: cart.item_count }} ``

Every user-facing string should go through t and live in the locale files. A string hard-coded into a template cannot be translated, and finding them all later is a job nobody enjoys — see localisation.

URLs

``liquid {{ 'base.css' | asset_url | stylesheet_tag }} {{ 'cart.js' | asset_url | script_tag }} {{ product.handle | url_for_type }} {{ collection.url }} {{ 'logo.svg' | asset_url }} ``

Never hard-code an asset path. And never hard-code /cart — use routes.cart_url, covered in Liquid objects.

Strings

``liquid {{ text | escape }} {{ text | strip_html }} {{ text | truncate: 120 }} {{ text | truncatewords: 20 }} {{ text | upcase }} {{ text | downcase }} {{ text | capitalize }} {{ text | handleize }} {{ text | replace: 'old', 'new' }} {{ text | strip }} {{ text | default: 'Untitled' }} {{ text | newline_to_br }} ``

escape on anything a customer wrote. Product descriptions you control; customer notes, review text and form input you don't.

default only fires on nil and empty string, not on whitespace. Test with blank when you mean "nothing meaningful".

Arrays

``liquid {{ product.tags | join: ', ' }} {{ collection.products | where: 'available', true }} {{ collection.products | sort: 'price' }} {{ product.images | first }} {{ product.images | last }} {{ product.variants | map: 'sku' }} {{ array | uniq }} {{ array | reverse }} {{ array | size }} {{ array | concat: other_array }} ``

The trap: where, sort and map on a large collection do the work in the template, every render. For a big catalogue that's a server-response problem. Filter at the data level where you can — an automated collection or a metafield — rather than fetching everything and discarding most of it.

Numbers and dates

``liquid {{ number | plus: 1 | minus: 2 | times: 3 | divided_by: 4 }} {{ number | round: 2 }} {{ number | ceil }} {{ number | floor }} {{ article.published_at | date: '%d %B %Y' }} {{ 'now' | date: '%Y' }} ``

divided_by does integer division when both operands are integers — a genuine source of quiet bugs. Force a float if you need one.

HTML helpers

``liquid {{ product.description | strip_html | truncate: 160 }} {{ page.content }} {{ form.errors | default_errors }} {{ 'summary' | link_to: product.url }} ``

strip_html | truncate is the standard pattern for meta descriptions when a product has no SEO description written — though a written one is always better.

The three that matter most

If you take three things: use money for every price, use image_tag for every image, and use t for every string. Those three habits fix most price bugs, most image performance problems and the entire localisation problem in one go.

Most Liquid bugs I find in audits are a price formatted by hand, an image tag written by hand, or a string typed straight into a template.

Is this the problem you’re looking at?

Send me the link to your store and a line about what is going wrong. You get a straight answer within one business day — no pitch, no obligation.

mario@clicksandcarts.co

Or see what I do around Shopify: services, work beyond the theme, selected work.

Keep reading

← All articles