Getting started
Introduction
Liquid is an open-source template language created by Shopify. It uses a combination of objects, tags, and filters to load dynamic content.
{{ product.title }}
{% if user %}
Hello {{ user.name }}!
{% endif %}
Basic Syntax
| Syntax | Purpose | Example |
|---|---|---|
{{ }} |
Output (objects/variables) | {{ page.title }} |
{% %} |
Logic (tags) | {% if user %} |
{%- -%} |
Whitespace control | {%- if user -%} |
{{ | }} |
Filters | {{ name | upcase }} |
Quick Example
{% assign name = "John" %}
<h1>Hello, {{ name | capitalize }}!</h1>
{% if name.size > 5 %}
<p>Long name!</p>
{% endif %}
<ul>
{% for i in (1..3) %}
<li>Item {{ i }}</li>
{% endfor %}
</ul>
Comments
{% comment %}
This is a multi-line comment.
It won't appear in the output.
{% endcomment %}
{% # This is an inline comment %}
{% #
Multi-line inline comment
supported as well
%}
Data Types
String
{% assign name = "John Doe" %}
{% assign greeting = 'Hello World' %}
{{ "Hello" }}
{{ 'Single quotes work too' }}
Number
{% assign age = 25 %}
{% assign price = 19.99 %}
{{ 42 }}
{{ 3.14 }}
Boolean
{% assign is_active = true %}
{% assign is_deleted = false %}
{% if is_active %}
Active!
{% endif %}
Nil
{% assign nothing = nil %}
{% if nothing == nil %}
Variable is nil
{% endif %}
Array
{% assign colors = "red, blue, green" | split: ", " %}
{{ colors[0] }} {% # red %}
{{ colors.first }} {% # red %}
{{ colors.size }} {% # 3 %}
EmptyDrop
Empty objects (like deleted products) return empty instead of nil.
{% if product == empty %}
Product was deleted
{% endif %}
Operators
Comparison
{% if age == 25 %}
{% if age != 30 %}
{% if age > 18 %}
{% if age < 65 %}
{% if age >= 21 %}
{% if age <= 100 %}
Logical
{% if age > 18 and age < 65 %}
Working age
{% endif %}
{% if status == "active" or status == "pending" %}
Show user
{% endif %}
Contains
{% if product.tags contains "sale" %}
On sale!
{% endif %}
{% if product.title contains "Pack" %}
Bundle product
{% endif %}
Order of Operations
{% # Parentheses not supported %}
{% # Use nested if statements %}
{% if status == "active" %}
{% if role == "admin" or role == "moderator" %}
Has access
{% endif %}
{% endif %}
Control Flow
if / elsif / else
{% if customer.name == "kevin" %}
Hey Kevin!
{% elsif customer.name == "anonymous" %}
Hey Anonymous!
{% else %}
Hi Stranger!
{% endif %}
unless
Opposite of if – executes if condition is false.
{% unless product.available %}
Sold out!
{% endunless %}
{% # Equivalent to: %}
{% if product.available == false %}
Sold out!
{% endif %}
case / when
{% assign handle = "cake" %}
{% case handle %}
{% when "cake" %}
This is a cake
{% when "cookie", "biscuit" %}
This is a cookie
{% else %}
This is not a cake nor a cookie
{% endcase %}
Iteration
for Loop
{% for product in collection.products %}
{{ product.title }}
{% endfor %}
for with Range
{% for i in (1..5) %}
{{ i }}
{% endfor %}
{% # Output: 1 2 3 4 5 %}
{% for i in (3..6) %}
{{ i }}
{% endfor %}
{% # Output: 3 4 5 6 %}
for Parameters
{% # limit: max iterations %}
{% for item in array limit:3 %}
{% # offset: start index %}
{% for item in array offset:2 %}
{% # reversed %}
{% for item in array reversed %}
{% # Combined %}
{% for item in array limit:3 offset:2 %}
for else
{% for product in collection.products %}
{{ product.title }}
{% else %}
No products in this collection.
{% endfor %}
break / continue
{% for i in (1..10) %}
{% if i == 5 %}
{% break %} {% # Stop loop %}
{% endif %}
{{ i }}
{% endfor %}
{% for i in (1..10) %}
{% if i == 5 %}
{% continue %} {% # Skip to next %}
{% endif %}
{{ i }}
{% endfor %}
forloop Object
{% for product in collection.products %}
{{ forloop.index }} {% # 1, 2, 3... %}
{{ forloop.index0 }} {% # 0, 1, 2... %}
{{ forloop.rindex }} {% # Reverse: 3, 2, 1 %}
{{ forloop.rindex0 }} {% # Reverse: 2, 1, 0 %}
{{ forloop.first }} {% # true on first %}
{{ forloop.last }} {% # true on last %}
{{ forloop.length }} {% # Total iterations %}
{% endfor %}
cycle
{% for item in items %}
<div class="{% cycle 'odd', 'even' %}">
{{ item }}
</div>
{% endfor %}
tablerow
<table>
{% tablerow product in collection.products cols:3 %}
{{ product.title }}
{% endtablerow %}
</table>
Generates <tr> and <td> tags automatically with cols parameter.
Variables
assign
{% assign name = "John" %}
{% assign age = 25 %}
{% assign total = price | times: quantity %}
{{ name }} {% # John %}
capture
{% capture greeting %}
Hello {{ customer.name }}!
Today is {{ "now" | date: "%A" }}.
{% endcapture %}
{{ greeting }}
increment / decrement
{% increment counter %} {% # 0 %}
{% increment counter %} {% # 1 %}
{% increment counter %} {% # 2 %}
{% decrement counter %} {% # -1 %}
{% decrement counter %} {% # -2 %}
Independent counter variables (don't use assign values).
Truthy and Falsy
Falsy Values
Only two values are falsy in Liquid:
{% if nil %}
{% # Won't execute %}
{% endif %}
{% if false %}
{% # Won't execute %}
{% endif %}
Truthy Values
Everything else is truthy, including:
{% # Empty strings are truthy! %}
{% if "" %}
This executes!
{% endif %}
{% # Zero is truthy %}
{% if 0 %}
This executes!
{% endif %}
{% # Empty arrays are truthy %}
{% if empty_array %}
This executes!
{% endif %}
Checking for Empty
{% # For strings %}
{% if name != blank %}
Name exists
{% endif %}
{% # For arrays %}
{% if array.size > 0 %}
Array has items
{% endif %}
{% # Using empty object %}
{% if product != empty %}
Product exists
{% endif %}
Template Tags
comment
{% comment %}
Multi-line comment
Won't be rendered
{% endcomment %}
raw
{% raw %}
{{ This won't be processed }}
{% Liquid tags ignored %}
{% endraw %}
liquid
{% liquid
assign name = "John"
if name == "John"
echo "Hello John!"
endif
%}
Allows multiple tags without {% %} delimiters.
echo
{% echo product.title %}
{% # Same as: %}
{{ product.title }}
Useful inside {% liquid %} blocks.
render
{% render "product-card" %}
{% render "product-card", product: item %}
{% render "snippet" with product %}
Renders a snippet file. Variables are isolated.
include (Deprecated)
{% include "snippet" %}
{% include "snippet", var: value %}
Deprecated – use render instead. Variables are shared with parent template.
Whitespace Control
Stripping Whitespace
{% # Without whitespace control: %}
{% if true %}
Hello
{% endif %}
{% # Output:
(blank line)
Hello
(blank line)
%}
{% # With whitespace control: %}
{%- if true -%}
Hello
{%- endif -%}
{% # Output: %}
Hello
Hyphen Position
| Syntax | Strips |
|---|---|
{%- |
Left whitespace |
-%} |
Right whitespace |
{{- |
Left whitespace |
-}} |
Right whitespace |
{%- assign name = "John" -%}
{{- name -}}
String Filters
Case
{{ "hello" | capitalize }}
{% # Hello %}
{{ "HELLO" | downcase }}
{% # hello %}
{{ "hello" | upcase }}
{% # HELLO %}
Manipulation
{{ " hello " | strip }}
{% # "hello" %}
{{ "hello" | lstrip }}
{% # "hello " %}
{{ "hello" | rstrip }}
{% # " hello" %}
{{ "Hello World" | remove: "World" }}
{% # "Hello " %}
{{ "Hello World" | remove_first: "l" }}
{% # "Helo World" %}
{{ "Hello" | replace: "e", "a" }}
{% # "Hallo" %}
Truncate
{{ "Hello World" | truncate: 8 }}
{% # "Hello..." %}
{{ "Hello World" | truncate: 8, "…" }}
{% # "Hello…" %}
{{ "Hello World" | truncatewords: 1 }}
{% # "Hello..." %}
Split
{% assign colors = "red,blue,green" | split: "," %}
{{ colors[0] }} {% # red %}
Escape
{{ "<p>Test</p>" | escape }}
{% # <p>Test</p> %}
{{ "Hello%20World" | url_decode }}
{% # "Hello World" %}
{{ "Hello World" | url_encode }}
{% # "Hello%20World" %}
Slugify
{{ "Hello World!" | slugify }}
{% # hello-world %}
{{ "Hello World!" | handleize }}
{% # hello-world (Shopify) %}
Array Filters
Access
{{ array | first }}
{{ array | last }}
{{ array[0] }}
{{ array | size }}
Manipulation
{{ array | reverse }}
{{ array | sort }}
{{ array | sort: "price" }}
{{ array | uniq }}
{{ array | compact }}
{% # Remove nil values %}
join
{% assign colors = "red,blue,green" | split: "," %}
{{ colors | join: " and " }}
{% # red and blue and green %}
map
{% assign titles = products | map: "title" %}
{% # Extract property from objects %}
where
{% assign available = products | where: "available", true %}
{% assign tagged = products | where: "tags", "sale" %}
concat
{% assign new_array = array1 | concat: array2 %}
Math Filters
Basic
{{ 4 | plus: 2 }} {% # 6 %}
{{ 4 | minus: 2 }} {% # 2 %}
{{ 4 | times: 2 }} {% # 8 %}
{{ 10 | divided_by: 2 }} {% # 5 %}
{{ 10 | modulo: 3 }} {% # 1 %}
Rounding
{{ 4.6 | ceil }} {% # 5 %}
{{ 4.6 | floor }} {% # 4 %}
{{ 4.6 | round }} {% # 5 %}
{{ 4.235 | round: 2 }} {% # 4.24 %}
Absolute
{{ -5 | abs }} {% # 5 %}
at_most / at_least
{{ 5 | at_most: 3 }} {% # 3 %}
{{ 5 | at_least: 7 }} {% # 7 %}
Other Filters
default
{{ product.title | default: "No title" }}
{% # Use fallback if value is nil/false/empty %}
date
{{ "now" | date: "%Y-%m-%d" }}
{% # 2026-02-05 %}
{{ article.published_at | date: "%B %d, %Y" }}
{% # February 05, 2026 %}
Format codes: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %a (weekday), %A (weekday full), %b (month abbr), %B (month full).
money
{{ 1000 | money }}
{% # $10.00 (Shopify only) %}
{{ 1000 | money_with_currency }}
{% # $10.00 USD (Shopify only) %}
JSON
{{ product | json }}
{% # Outputs JSON representation %}
slice
{{ "hello" | slice: 0 }} {% # h %}
{{ "hello" | slice: 1, 3 }} {% # ell %}
{{ array | slice: 0, 3 }} {% # First 3 items %}
Examples
Blog Post Template
{% for post in site.posts limit:5 %}
<article>
<h2>
<a href="{{ post.url }}">
{{ post.title | escape }}
</a>
</h2>
<time datetime="{{ post.date | date_to_xmlschema }}">
{{ post.date | date: "%B %d, %Y" }}
</time>
{% if post.excerpt %}
<p>{{ post.excerpt | strip_html | truncatewords: 30 }}</p>
{% endif %}
{% if post.tags.size > 0 %}
<ul class="tags">
{% for tag in post.tags %}
<li>{{ tag }}</li>
{% endfor %}
</ul>
{% endif %}
</article>
{% endfor %}
Product Listing (Shopify)
{% if collection.products.size > 0 %}
<div class="products">
{% for product in collection.products %}
<div class="product-card">
{% if product.featured_image %}
<img src="{{ product.featured_image | img_url: 'medium' }}"
alt="{{ product.title | escape }}">
{% endif %}
<h3>{{ product.title }}</h3>
<p class="price">
{% if product.compare_at_price > product.price %}
<span class="sale">
{{ product.price | money }}
</span>
<span class="original">
{{ product.compare_at_price | money }}
</span>
{% else %}
{{ product.price | money }}
{% endif %}
</p>
{% if product.available %}
<button>Add to Cart</button>
{% else %}
<button disabled>Sold Out</button>
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<p>No products in this collection.</p>
{% endif %}
Conditional Navigation
<nav>
<ul>
{% for link in linklists.main-menu.links %}
<li class="{% if link.active %}active{% endif %}">
<a href="{{ link.url }}">
{{ link.title }}
</a>
{% if link.links.size > 0 %}
<ul class="dropdown">
{% for child_link in link.links %}
<li>
<a href="{{ child_link.url }}">
{{ child_link.title }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
Pagination
{% if paginate.pages > 1 %}
<nav class="pagination">
{% if paginate.previous %}
<a href="{{ paginate.previous.url }}">Previous</a>
{% else %}
<span class="disabled">Previous</span>
{% endif %}
{% for part in paginate.parts %}
{% if part.is_link %}
<a href="{{ part.url }}">{{ part.title }}</a>
{% else %}
{% if part.title == paginate.current_page %}
<span class="current">{{ part.title }}</span>
{% else %}
<span>{{ part.title }}</span>
{% endif %}
{% endif %}
{% endfor %}
{% if paginate.next %}
<a href="{{ paginate.next.url }}">Next</a>
{% else %}
<span class="disabled">Next</span>
{% endif %}
</nav>
{% endif %}
Jekyll vs Shopify
Jekyll-Specific Tags
{% highlight ruby %}
def hello
puts "Hello"
end
{% endhighlight %}
{% link _posts/2020-01-01-post.md %}
{% post_url 2020-01-01-post %}
Jekyll includes syntax highlighting, post linking, and additional tags.
Shopify-Specific
{% form 'product', product %}
{% # Shopify form helper %}
{% endform %}
{% section 'header' %}
{% # Render section files %}
{% paginate collection.products by 12 %}
{% # Shopify pagination %}
{% endpaginate %}
Shopify has forms, sections, themes, and e-commerce objects (products, variants, cart, checkout).
Common Differences
| Feature | Jekyll | Shopify |
|---|---|---|
include |
Supported | Deprecated (use render) |
site object |
Available | Not available |
page object |
Available | Use article/product/etc |
content variable |
Available | Use {{ content_for_layout }} |
| Collections | site.posts, etc |
collection.products, etc |
Gotchas
Empty Strings Are Truthy
{% assign name = "" %}
{% if name %}
{% # This executes! Empty string is truthy %}
{% endif %}
{% # Check for blank instead: %}
{% if name != blank %}
{% # This won't execute %}
{% endif %}
No Parentheses in Logic
{% # This won't work: %}
{% if (x == 1 or x == 2) and y == 3 %}
{% # Use nested if instead: %}
{% if y == 3 %}
{% if x == 1 or x == 2 %}
{% # code %}
{% endif %}
{% endif %}
Division Returns Integer
{{ 10 | divided_by: 4 }}
{% # 2, not 2.5 %}
{% # Force float division: %}
{{ 10.0 | divided_by: 4 }}
{% # 2.5 %}
forloop Index Scope
{% for item in items %}
{{ forloop.index }}
{% endfor %}
{% # forloop object only available inside loops %}
{{ forloop.index }} {% # Error! %}
assign vs capture
{% # assign: single value %}
{% assign name = "John" %}
{% # capture: build string with logic %}
{% capture full_name %}
{{ first }} {{ last }}
{% endcapture %}
capture useful for complex string building, assign for simple values.
Variable Scoping with render
{% assign color = "red" %}
{% render "snippet" %}
{% # snippet can't access 'color' %}
{% render "snippet", color: color %}
{% # Pass explicitly %}
render creates isolated scope. Use include (deprecated) or pass variables explicitly.
Also see
- Liquid Documentation (shopify.github.io)
- Shopify Liquid Reference (shopify.dev)
- Jekyll Liquid Documentation (jekyllrb.com)
- Liquid GitHub Repository (github.com)
- Liquid Cheat Sheet (PDF) (shopify.com)