Files
2025-11-29 18:32:51 +08:00

11 KiB

Liquid Syntax - Complete Reference

Tag Categories

Control Flow Tags

if/elsif/else/endif

{% if product.available %}
  <button>Add to Cart</button>
{% elsif product.coming_soon %}
  <p>Coming Soon</p>
{% else %}
  <p>Sold Out</p>
{% endif %}

Operators:

  • == - equals
  • != - not equals
  • > - greater than
  • < - less than
  • >= - greater than or equal
  • <= - less than or equal
  • contains - substring or array contains
  • and - logical AND
  • or - logical OR

Examples:

{% if product.price > 100 and product.available %}
  Premium item in stock
{% endif %}

{% if product.tags contains 'sale' or product.type == 'clearance' %}
  On sale!
{% endif %}

unless

Negated if statement:

{% unless customer.name == blank %}
  Hello, {{ customer.name }}
{% endunless %}

{# Equivalent to: #}
{% if customer.name != blank %}
  Hello, {{ customer.name }}
{% endif %}

case/when

Switch-case statement:

{% case product.type %}
  {% when 'shoes' %}
    <icon>👟</icon>
  {% when 'boots' %}
    <icon>👢</icon>
  {% when 'sneakers' %}
    <icon>👟</icon>
  {% else %}
    <icon>📦</icon>
{% endcase %}

Iteration Tags

for loop

{% for product in collection.products %}
  {{ product.title }}
{% endfor %}

Modifiers:

{# Limit to first 5 #}
{% for product in collection.products limit: 5 %}
  {{ product.title }}
{% endfor %}

{# Skip first 10 #}
{% for product in collection.products offset: 10 %}
  {{ product.title }}
{% endfor %}

{# Reverse order #}
{% for product in collection.products reversed %}
  {{ product.title }}
{% endfor %}

{# Combine modifiers #}
{% for product in collection.products limit: 5 offset: 10 %}
  {# Items 11-15 #}
{% endfor %}

forloop object (available inside loops):

{% for item in array %}
  {{ forloop.index }}        {# 1-based: 1, 2, 3, ... #}
  {{ forloop.index0 }}       {# 0-based: 0, 1, 2, ... #}
  {{ forloop.rindex }}       {# Reverse 1-based: 3, 2, 1 #}
  {{ forloop.rindex0 }}      {# Reverse 0-based: 2, 1, 0 #}
  {{ forloop.first }}        {# true on first iteration #}
  {{ forloop.last }}         {# true on last iteration #}
  {{ forloop.length }}       {# Total number of items #}
{% endfor %}

Example usage:

{% for product in collection.products %}
  {% if forloop.first %}
    <h2>Featured Product</h2>
  {% endif %}

  <div class="product-{{ forloop.index }}">
    {{ product.title }}
  </div>

  {% if forloop.index == 3 %}
    <hr> {# Divider after 3rd item #}
  {% endif %}

  {% if forloop.last %}
    <p>Showing {{ forloop.length }} products</p>
  {% endif %}
{% endfor %}

break and continue

{% for product in collection.products %}
  {% if product.handle == 'target' %}
    {% break %}  {# Exit loop entirely #}
  {% endif %}

  {% if product.available == false %}
    {% continue %}  {# Skip to next iteration #}
  {% endif %}

  {{ product.title }}
{% endfor %}

tablerow

Creates HTML table rows:

{% tablerow product in collection.products cols: 3 %}
  {{ product.title }}
{% endtablerow %}

{# Output: #}
<table>
  <tr class="row1">
    <td class="col1">Product 1</td>
    <td class="col2">Product 2</td>
    <td class="col3">Product 3</td>
  </tr>
  <tr class="row2">
    <td class="col1">Product 4</td>
    ...
  </tr>
</table>

tablerow object:

{% tablerow product in products cols: 3 limit: 12 %}
  {{ tablerow.col }}         {# Current column (1-based) #}
  {{ tablerow.col0 }}        {# Current column (0-based) #}
  {{ tablerow.row }}         {# Current row (1-based) #}
  {{ tablerow.index }}       {# Item index (1-based) #}
  {{ tablerow.first }}       {# true on first item #}
  {{ tablerow.last }}        {# true on last item #}
  {{ tablerow.col_first }}   {# true on first column #}
  {{ tablerow.col_last }}    {# true on last column #}
{% endtablerow %}

paginate

For paginating large collections:

{% paginate collection.products by 12 %}

  {% for product in paginate.collection.products %}
    {% render 'product-card', product: product %}
  {% endfor %}

  {# Pagination controls #}
  {% if paginate.pages > 1 %}
    {{ paginate | default_pagination }}
  {% endif %}

{% endpaginate %}

paginate object:

{{ paginate.current_page }}      {# Current page number #}
{{ paginate.pages }}              {# Total pages #}
{{ paginate.items }}              {# Total items #}
{{ paginate.page_size }}          {# Items per page #}

{{ paginate.previous.url }}       {# Previous page URL (if exists) #}
{{ paginate.previous.title }}     {# Previous page title #}
{{ paginate.previous.is_link }}   {# Boolean #}

{{ paginate.next.url }}           {# Next page URL (if exists) #}
{{ paginate.next.title }}         {# Next page title #}
{{ paginate.next.is_link }}       {# Boolean #}

{{ paginate.parts }}              {# Array of page links #}

Custom pagination:

{% paginate collection.products by 20 %}

  <div class="pagination">
    {% if paginate.previous %}
      <a href="{{ paginate.previous.url }}">← Previous</a>
    {% endif %}

    {% for part in paginate.parts %}
      {% if part.is_link %}
        <a href="{{ part.url }}">{{ part.title }}</a>
      {% else %}
        <span class="current">{{ part.title }}</span>
      {% endif %}
    {% endfor %}

    {% if paginate.next %}
      <a href="{{ paginate.next.url }}">Next →</a>
    {% endif %}
  </div>

{% endpaginate %}

Variable Assignment

assign

Single-line variable assignment:

{% assign sale_price = product.price | times: 0.8 %}
{% assign is_available = product.available %}
{% assign product_count = collection.products.size %}
{% assign full_name = customer.first_name | append: ' ' | append: customer.last_name %}

capture

Multi-line content capture:

{% capture product_title %}
  {{ collection.title }} - {{ product.title }}
{% endcapture %}

{{ product_title }}  {# "Summer Sale - Blue T-Shirt" #}

{% capture greeting %}
  <h1>Welcome, {{ customer.name }}!</h1>
  <p>You have {{ customer.orders_count }} orders.</p>
{% endcapture %}

{{ greeting }}

liquid (multi-statement)

Cleaner syntax for multiple statements:

{% liquid
  assign product_type = product.type
  assign is_on_sale = product.on_sale
  assign sale_percentage = product.discount_percent

  if is_on_sale
    assign status = 'SALE'
  else
    assign status = 'REGULAR'
  endif

  echo status
%}

Template Inclusion

render

Isolated scope (preferred method):

{# Basic usage #}
{% render 'product-card', product: product %}

{# Multiple parameters #}
{% render 'product-card',
  product: product,
  show_price: true,
  show_vendor: false,
  css_class: 'featured'
%}

{# Render for each item #}
{% render 'product-card' for collection.products as item %}

{# Pass arrays #}
{% render 'gallery', images: product.images %}

Inside product-card.liquid:

{# Only has access to passed parameters #}
<div class="product {% if css_class %}{{ css_class }}{% endif %}">
  <h3>{{ product.title }}</h3>

  {% if show_price %}
    <p>{{ product.price | money }}</p>
  {% endif %}

  {% if show_vendor %}
    <p>{{ product.vendor }}</p>
  {% endif %}
</div>

include

Shared scope (legacy, avoid in new code):

{% include 'product-details' %}

{# Can access all parent template variables #}
{# Harder to debug and reason about #}

section

Load dynamic sections:

{% section 'featured-product' %}
{% section 'newsletter-signup' %}

Utility Tags

comment

Multi-line comments:

{% comment %}
  This entire block is ignored
  by the Liquid renderer.
  Use for documentation.
{% endcomment %}

{# Single-line comment #}

echo

Output shorthand (alternative to {{ }}):

{% echo product.title %}
{# Equivalent to: {{ product.title }} #}

raw

Output Liquid code without processing:

{% raw %}
  {{ This will be output as-is }}
  {% Liquid tags won't be processed %}
{% endraw %}

Useful for documentation or code examples.

Whitespace Control

Strip whitespace using hyphens:

{%- if condition -%}
  Content (whitespace stripped on both sides)
{%- endif -%}

{{ "hello" -}}world
{# Output: helloworld (no space) #}

{{- product.title }}
{# Strips whitespace before output #}

{{ product.title -}}
{# Strips whitespace after output #}

Example:

{# Without whitespace control: #}
{% for item in array %}
  {{ item }}
{% endfor %}

{# Output has newlines and indentation #}

{# With whitespace control: #}
{%- for item in array -%}
  {{ item }}
{%- endfor -%}

{# Output is compact #}

Operator Precedence

Order of evaluation (right-to-left):

{% if true or false and false %}
  {# Evaluates as: true or (false and false) = true #}
{% endif %}

IMPORTANT: No parentheses support in Liquid. Break complex conditions into variables:

{# ❌ DOESN'T WORK: #}
{% if (x > 5 and y < 10) or z == 0 %}

{# ✅ WORKS: #}
{% assign condition1 = false %}
{% if x > 5 and y < 10 %}
  {% assign condition1 = true %}
{% endif %}

{% if condition1 or z == 0 %}
  {# Logic here #}
{% endif %}

Performance Tips

  1. Cache repeated calculations:
{# ❌ Inefficient: #}
{% for i in (1..10) %}
  {{ collection.products.size }}  {# Calculated 10 times #}
{% endfor %}

{# ✅ Efficient: #}
{% assign product_count = collection.products.size %}
{% for i in (1..10) %}
  {{ product_count }}
{% endfor %}
  1. Use limit and offset instead of iterating full arrays:
{# ❌ Inefficient: #}
{% for product in collection.products %}
  {% if forloop.index <= 5 %}
    {{ product.title }}
  {% endif %}
{% endfor %}

{# ✅ Efficient: #}
{% for product in collection.products limit: 5 %}
  {{ product.title }}
{% endfor %}
  1. Prefer render over include for better performance and variable scoping

  2. Use liquid tag for cleaner multi-statement blocks

Common Gotchas

  1. No parentheses in conditions - Use variables instead
  2. Right-to-left evaluation - Be careful with operator precedence
  3. String concatenation - Use append filter or capture tag
  4. Array/object mutation - Not possible; create new variables
  5. Integer division - {{ 5 | divided_by: 2 }} returns 2, not 2.5
  6. Truthy/falsy values:
    • false and nil are falsy
    • Everything else (including 0, "", []) is truthy

Debugging Tips

  1. Output variable types:
{{ product | json }}  {# Output entire object as JSON #}
{{ product.class }}   {# Output object type #}
{{ variable.size }}   {# Check array/string length #}
  1. Check for nil/existence:
{% if product.metafield %}
  Metafield exists
{% else %}
  Metafield is nil
{% endif %}
  1. Use default filter for safety:
{{ product.metafield.value | default: "Not set" }}
  1. Enable theme preview console to see Liquid errors in real-time