NexusCS

Liquid

Markup
Quick reference for Liquid, the safe, customer-facing template language by Shopify. Used in Shopify themes and Jekyll sites.
shopify
jekyll
template

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 }}
{% # &lt;p&gt;Test&lt;/p&gt; %}

{{ "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