In brief
Liquid is the template language for Shopify themes: it runs server-side and injects your data, products, prices, and inventory into the HTML seen by the visitor. It's built on three complementary components: objects that display data, tags that carry logic, and filters that transform the rendering. Its simple uses, like modifying text, applying a filter, or adding a small condition, are within reach for non-developers, while complex loops, cart, and checkout functionalities require a true specialist. At Stellar, we've been using Liquid since 2018 with a consistent approach: starting from a solid existing theme and reserving custom development for customizations that truly increase conversion, rather than recoding everything from scratch.
Summary
Shopify Liquid is the open-source template language that builds the platform's themes. Its role can be summarized in one sentence: it connects your store data (products, collections, cart, customer) to the HTML displayed on the screen, and decides what to show, to whom, and in what order. Without Liquid, a Shopify theme would just be a static page, unable to display the correct price or inventory in real-time.
Every time a product title, a promotion badge, or an inventory counter updates automatically, Liquid is working behind the scenes. Understanding it, even without becoming a developer, gives you control over your store's appearance and behavior. This page provides you with the concrete basics, with ready-to-read code, and then helps you understand where healthy tinkering ends and developer work begins.
At Stellar, we've been using Liquid since our beginnings in 2018. Our approach after delivering over 100 stores: we do custom development, but we almost always start from a solid existing theme rather than recoding everything from a blank page. Rewriting a functionality in Liquid that is already native to a good theme is a waste of client budget. Custom development is even more valuable when it focuses on where it truly changes the experience and conversion.
Liquid, what is it exactly?
Liquid is a template language: it doesn't calculate, it assembles. Shopify created it in 2010, then made it open source, so it's now found far beyond e-commerce. In a store, it runs server-side, before the page arrives in the browser: the visitor never sees the Liquid code, only its HTML output.
This is the key difference from JavaScript, which executes in the browser once the page has loaded. Liquid determines the page's content (which product, what price, what availability), while JavaScript adds interactivity on top (a menu that opens, a cart that updates without reloading). The two complement each other; they are not in opposition.
In practice, Liquid serves three purposes:
- Display dynamic data: a product name, its price, an image, the first name of the logged-in customer.
- Decide with logic: show a block only if the product is on sale, in stock, or tagged in a certain way.
- Transform the display: format a price in euros, shorten a description, resize an image on the fly.
These three uses correspond exactly to the three building blocks of the language. To place Liquid within the overall ecosystem, our complete guide to succeeding on Shopify puts themes in the broader context of the platform.
The 3 components: objects, tags and filters
All of Liquid comes down to three syntax elements. Objects display data, tags carry logic, and filters modify the rendering. Once you understand this trio, you'll be able to read 90% of Shopify themes without difficulty.
Objects: displaying data
An object is recognized by its double curly braces. It refers to a store's data and displays it as is. It is the simplest and most used building block.
{{ product.title }}
{{ product.price | money }}
{{ collection.title }}
{{ customer.first_name }}
Here, product, collection or customer are objects provided by Shopify depending on the context of the page. Some custom data passes through metafields: we detail their display in our guide dedicated to Shopify metafields and their display in Liquid.
Tags: controlling logic
A tag is recognized by the curly brace followed by a percentage sign. It doesn't display anything by itself: it decides. Condition, loop, variable assignment, it's the brain of the theme.
{% if product.available %}
In stock, ships within 48 hours
{% else %}
Back in stock soon
{% endif %}
{% for produit in collection.products %}
{{ produit.title }}
{% endfor %}
The if condition displays a block only if a rule is true. The for loop repeats a block for each element in a list, for example, each product in a collection. These are the two tags you'll encounter most often.
Filters: transforming the display
A filter is placed after a vertical bar, inside an object. It takes a value and transforms it before display, without touching the original data. They can be chained.
{{ product.price | money }}
{{ product.title | upcase }}
{{ article.published_at | date: '%d/%m/%Y' }}
{{ product.description | strip_html | truncate: 120 }}
On the last line, two filters are chained: strip_html removes HTML tags from the description, then truncate cuts it to 120 characters. It's this chaining logic that makes filters so practical for everyday use.
Cheat sheet: 15 useful Liquid filters
Here are the filters our developers use most often, those that cover the vast majority of store needs. Keep this table handy when you open a theme file.
| Filter | What it does | Example |
|---|---|---|
| money | Formats an amount according to the store's currency | {{ 1990 | money }} gives €19.90 |
| money_without_trailing_zeros | Like money, but without unnecessary cents | {{ 2000 | money_without_trailing_zeros }} gives €20 |
| date | Formats a date to the desired format | {{ article.published_at | date: '%d/%m/%Y' }} |
| default | Displays a fallback value if the data is empty | {{ product.title | default: 'Untitled' }} |
| truncate | Truncates text to a number of characters | {{ product.description | truncate: 120 }} |
| truncatewords | Truncates text to a number of words | {{ product.description | truncatewords: 20 }} |
| strip_html | Removes all HTML tags from text | {{ product.description | strip_html }} |
| escape | Secures text displayed in HTML | {{ comment.author | escape }} |
| upcase / downcase | Converts text to uppercase or lowercase | {{ product.vendor | upcase }} |
| capitalize | Capitalizes the first word | {{ product.type | capitalize }} |
| image_url | Generates the URL of an image at the requested size | {{ product.featured_image | image_url: width: 600 }} |
| asset_url | Returns the URL of a theme file | {{ 'logo.svg' | asset_url }} |
| size | Counts the elements in a list or text | {{ cart.items | size }} item(s) |
| plus / minus / times | Performs a simple calculation | {{ product.price | times: 2 }} |
| handleize | Transforms text into a clean URL identifier | {{ 'Robe d\'été' | handleize }} gives robe-d-ete |
The complete and up-to-date list lives in the official Shopify Liquid documentation, a reference to keep bookmarked.
Practical case: a conditional "New" badge
Nothing beats a complete example. Objective: display a "New" badge on product pages, without a paid app and without overloading the theme. Two approaches, from the simplest to the most automatic.
Approach 1, by tag. You add the "new" tag to the relevant products from the admin, and the theme displays the badge accordingly. Simple, manually controlled, ideal for occasional highlights.
{% if product.tags contains 'new' %}
<span class="badge badge--new">New</span>
{% endif %}
Approach 2, by date. The badge appears automatically for any product created less than 14 days ago, then disappears on its own. No tags to manage, but a bit more logic.
{% assign now = 'now' | date: '%s' | plus: 0 %}
{% assign creation = product.created_at | date: '%s' | plus: 0 %}
{% assign age = now | minus: creation %}
{% if age < 1209600 %}
<span class="badge badge--new">New</span>
{% endif %}
In the second block, we convert the current time and the product's creation date into seconds, calculate the difference, then compare it to 1,209,600 seconds, which is 14 days. This is exactly the kind of discreet automation that saves time in the long run: no one has to remember to remove the tag.
The style of the badge (color, position, roundedness) is then managed with CSS in the theme. It's also at this stage that a developer's eye avoids pitfalls: a misplaced badge that breaks the product grid on mobile, or a loop that's too heavy on a collection with several hundred references.
Do it yourself or hire a developer?
The good news: some parts of Liquid are perfectly accessible without being a developer. The limit lies where an error can break the display in production or slow down the store. Here's our dividing line, the one we apply internally.
| You can do it yourself | Better to entrust to a developer |
|---|---|
| Modify text or labels in a theme file | Create or redesign an entire section with its settings |
| Add a simple filter (money, date, truncate) | Write loops on large catalogs without slowing down performance |
| Set a short if/else condition and test it in pre-production | Touch the cart, checkout, or JSON templates |
| Duplicate an existing block that already works | Integrate business logic or a third-party app in depth |
Our safety rule is simple: duplicate the theme before any intervention, and test on the copy. A published theme is modified without a safety net, and a forgotten curly brace is enough to display an error to your visitors.
When Liquid is no longer sufficient, for example for a store with a very rich interface or performance needs, the next step is often a decoupled architecture: we explain this in our guide on the Hydrogen framework, the React alternative to Liquid. For truly impactful custom personalizations, you can also rely on our Shopify development team: we start from your current theme to invest only where custom development creates value.