Skip to main content

Liquid Reference

EasyOrders custom themes use LiquidJS — a JavaScript implementation of the Liquid template language. Each section file (.liquid) is a Liquid template that gets rendered with store data injected as variables.

Template Syntax

Output: {{ }}

Print a variable's value:

<h1>{{ product_name }}</h1>
<span>{{ price }} {{ currency }}</span>

Tags: {% %}

Control flow and logic (not printed):

{% if sale_price and sale_price < price %}
<span class="on-sale">{{ sale_price }} {{ currency }}</span>
{% else %}
<span>{{ price }} {{ currency }}</span>
{% endif %}

Comments: {% comment %}

{% comment %} This will not appear in the output {% endcomment %}

Variables and Scope

Auto-Injected: theme_data

Every section template automatically receives theme_data — the merchant-configured values from your schema.json. You never need to pass it explicitly:

<h1 style="color: {{ theme_data.color_primary }}">
{{ theme_data.hero_headline }}
</h1>

Section-Specific Variables

Each section also receives its own set of variables (documented in Layout sections, Product sections, Home sections, and Utility sections). For example, product-details.liquid receives product_name, price, sale_price, currency, rating, and reviews_count.

Assigning Variables

Create local variables with assign:

{% assign full_stars = rating | floor %}
{% assign discount = price | minus: sale_price | times: 100 | divided_by: price | floor %}

<span>-{{ discount }}%</span>

Control Flow

if / elsif / else

{% if sale_price and sale_price < price %}
<span class="sale">{{ sale_price }} {{ currency }}</span>
{% elsif price > 0 %}
<span>{{ price }} {{ currency }}</span>
{% else %}
<span>Free</span>
{% endif %}

unless

The inverse of if:

{% unless hide_quantity %}
<div class="quantity-picker">...</div>
{% endunless %}

case / when

{% case announcement_config.type %}
{% when "marquee" %}
<div class="marquee">...</div>
{% when "slider" %}
<div class="slider">...</div>
{% else %}
<div class="simple">...</div>
{% endcase %}

Loops

for

{% for product in products %}
<div class="product-card">
<h3>{{ product.name }}</h3>
<span>{{ product.price }} {{ currency }}</span>
</div>
{% endfor %}

for with limit

{% for product in products limit: 4 %}
...
{% endfor %}

forloop Object

Inside a for loop you have access to the forloop object:

PropertyTypeDescription
forloop.indexnumberCurrent iteration (1-based)
forloop.index0numberCurrent iteration (0-based)
forloop.firstbooleanIs this the first iteration?
forloop.lastbooleanIs this the last iteration?
forloop.lengthnumberTotal number of items
{% for item in announcement_config.text %}
<span>{{ item }}</span>
{% unless forloop.last %}
<span class="separator">&mdash;</span>
{% endunless %}
{% endfor %}

Filters

Filters transform a value. Chain them with |:

{{ "hello world" | upcase }}           → HELLO WORLD
{{ price | floor }} → 29
{{ price | minus: sale_price }} → 10

Common Filters

FilterDescriptionExample
floorRound down{{ 4.7 | floor }}4
ceilRound up{{ 4.1 | ceil }}5
roundRound to nearest{{ 4.5 | round }}5
plusAdd{{ 5 | plus: 3 }}8
minusSubtract{{ 10 | minus: 3 }}7
timesMultiply{{ 5 | times: 2 }}10
divided_byDivide{{ 10 | divided_by: 3 }}3
moduloRemainder{{ 10 | modulo: 3 }}1
sizeArray/string length{{ images.size }}5
firstFirst element{{ images | first }}
lastLast element{{ images | last }}
whereFilter array by key{{ variations | where: "type", "color" | first }}
joinJoin array{{ tags | join: ", " }}
splitSplit string{{ "a,b,c" | split: "," }}
upcaseUppercase{{ name | upcase }}
downcaseLowercase{{ name | downcase }}
stripTrim whitespace{{ text | strip }}
replaceReplace substring{{ name | replace: " ", "-" }}
containsCheck substring (in if){% if url contains ".mp4" %}
defaultFallback value{{ title | default: "Untitled" }}

Chaining Filters

{% assign discount = price | minus: sale_price | times: 100 | divided_by: price | floor %}
<span class="badge">-{{ discount }}%</span>

Common Patterns

Conditional CSS Classes

<div class="slide{% if forloop.first %} active{% endif %}">
{{ slide.title }}
</div>

Image vs Video Detection

{% if mainImage contains '.mp4' %}
<video src="{{ mainImage }}" controls autoplay muted></video>
{% else %}
<img src="{{ mainImage }}" alt="{{ product_name }}" />
{% endif %}

Inline Style from theme_data

<section style="--primary: {{ theme_data.color_primary }};">
...
</section>

Product Variations (Color Swatches)

{% assign color_variation = product.variations | where: "type", "color" | first %}
{% if color_variation and color_variation.props %}
{% for prop in color_variation.props limit: 4 %}
<span class="swatch" style="background: {{ prop.value }}" title="{{ prop.name }}"></span>
{% endfor %}
{% endif %}

Star Rating

{% assign full_stars = rating | floor %}
{% assign decimal = rating | minus: full_stars %}
{% assign has_half = false %}
{% if decimal >= 0.25 and decimal < 0.75 %}
{% assign has_half = true %}
{% elsif decimal >= 0.75 %}
{% assign full_stars = full_stars | plus: 1 %}
{% endif %}

{% for i in (1..5) %}
{% if i <= full_stars %}
<span class="star filled"></span>
{% elsif has_half and i == full_stars | plus: 1 %}
<span class="star half"></span>
{% else %}
<span class="star empty"></span>
{% endif %}
{% endfor %}

Internal links in your Liquid templates are automatically intercepted for SPA (single-page app) navigation. When a user clicks a link like /products/my-product or /collections/shoes, the storefront uses client-side routing instead of a full page reload.

This happens automatically for same-origin <a> tags in sections that have link interception enabled (most sections). You don't need to do anything special — just use standard <a href="..."> tags:

<a href="/collections/{{ category.slug }}">{{ category.name }}</a>
<a href="/products/{{ product.slug }}">{{ product.name }}</a>
warning

External links (different domain) are not intercepted and work normally. If you need a link to open in a new tab, add target="_blank" rel="noreferrer" as usual.