Skip to main content

Fluid Reference

Fluid is the templating language you use to customise your site's appearance — page layouts, snippets, product pages, blog templates, email designs. This guide is for theme designers and content editors.

N
Written by Neil Fernandes

If you can read HTML, you can write Fluid. The language only adds two new pieces of syntax to plain HTML, and the rest is just rules about how those two pieces behave.


1. The basics

A Fluid template is plain HTML (or text) with two extra pieces of syntax mixed in:

Construct

Purpose

Example

{{ ... }}

Display a value from your site (a product name, a customer's email, etc.)

Hello, {{ customer.first_name }}!

{% ... %}

Do something — show content only if a condition is true, repeat content for every item in a list, store a value to reuse later

{% if cart.item_count > 0 %}...{% endif %}

Anything outside those two markers is left exactly as you wrote it.

Show a value

<h1>{{ product.title }}</h1>
<p>{{ product.description }}</p>

Get at nested values

Values are often grouped — a customer has an address, an address has a city. Walk into them with dots:

{{ user.address.city }}
{{ data['first-name'] }}
{{ items[0] }}

Use square brackets [...] only when the name contains characters that dots can't handle (like a hyphen), or to pick a specific position out of a list.

Transform a value with filters

A filter changes how a value is displayed — uppercase it, format it as money, shorten it, and so on. Add a filter with the pipe character |. You can chain as many as you want, left to right:

{{ 'hello world' | upcase }}                  → HELLO WORLD
{{ product.price | money }} → $19.99
{{ article.body | strip_html | truncate:120 }}

Some filters take extra information. Put it after a colon, and separate multiple values with commas:

{{ 'one two three' | replace:'two','TWO' }}   → one TWO three

The full list of filters is in Section 7.


2. Logic

if / elsif / else

{% if customer %}
Welcome back, {{ customer.first_name }}.
{% elsif visitor.first_visit %}
Welcome!
{% else %}
Hello again.
{% endif %}

Available operators: ==, !=, <, >, <=, >=, combined with and / or.

{% if product.available and product.price < 50 %}
On sale!
{% endif %}

elseif is also accepted as a spelling of elsif.

unless

The inverse of if. Renders the block when the condition is false.

{% unless customer.tax_exempt %}
Tax: {{ order.tax | money }}
{% endunless %}

case / when

A cleaner alternative to a long if/elsif chain when you're comparing a single value:

{% case order.status %}
{% when 'paid' %}
Thanks — your order is on the way.
{% when 'refunded' %}
Your refund has been processed.
{% when 'cancelled' %}
This order was cancelled.
{% else %}
Order status: {{ order.status }}
{% endcase %}

Truthy / falsy

These values count as false: missing variables, null, the literal false, and empty strings. Everything else is true — including the number 0. If you need to test for a non-zero number, compare it explicitly with >:

{% if cart.item_count > 0 %}...{% endif %}    <!-- good -->
{% if cart.item_count %}...{% endif %} <!-- also true when count is 0 — watch out -->

3. Loops

for

<ul>
{% for product in collection.products %}
<li>{{ product.title }} — {{ product.price | money }}</li>
{% endfor %}
</ul>

Loop options

{% for item in items limit:5 %}...{% endfor %}
{% for item in items offset:10 %}...{% endfor %}
{% for item in items reversed %}...{% endfor %}

Number ranges

{% for i in (1..5) %}
Page {{ i }}
{% endfor %}

Inside the loop

The forloop object gives you positional info:

Property

Meaning

forloop.index

1-based index

forloop.index0

0-based index

forloop.first

true on the first iteration

forloop.last

true on the last iteration

forloop.length

Total iterations

{% for item in items %}
{% if forloop.first %}<ul>{% endif %}
<li>{{ forloop.index }}. {{ item.title }}</li>
{% if forloop.last %}</ul>{% endif %}
{% endfor %}

tablerow

Renders an HTML table row per item, with configurable columns:

<table>
{% tablerow product in collection.products cols:3 %}
{{ product.title }}
{% endtablerow %}
</table>

cycle

Rotate through a list of values across iterations. Useful for striped rows:

{% for row in rows %}
<tr class="{% cycle 'odd', 'even' %}">...</tr>
{% endfor %}

You can run independent cycles in parallel by naming them:

{% cycle 'colours': 'red', 'blue' %}
{% cycle 'sizes': 'small', 'large' %}

paginate

Wrap a loop to enable pagination. Inside the block, a paginate object becomes available:

{% paginate collection.products by 12 %}
{% for product in collection.products %}
<h3>{{ product.title }}</h3>
{% endfor %}

{{ paginate | default_pagination }}
{% endpaginate %}

The paginate object has current_page, pages, next, previous, and parts for building your own pager. Or just pipe it through default_pagination / foundation_pagination.


4. Variables

assign

Create or overwrite a variable. Filters can be chained on the right-hand side:

{% assign greeting = 'hello' %}
{% assign loud_greeting = greeting | upcase %}
{% assign full_price = product.price | plus: product.shipping %}

capture

Build up a block of content and store the result in a variable. Anything between {% capture %} and {% endcapture %} is rendered and saved instead of being shown:

{% capture email_subject %}
Order {{ order.number }} — {{ shop.name }}
{% endcapture %}

<title>{{ email_subject | strip_newlines | trim }}</title>

increment / decrement

Auto-incrementing (or decrementing) counters. Each time you write {% increment widget_id %} on a page, the number goes up by one. Handy for generating unique IDs for things like accordion sections or modal dialogs:

{% increment widget_id %}      → 0
{% increment widget_id %} → 1
{% increment widget_id %} → 2

5. Snippets (includes)

Themes are usually broken up into reusable pieces — a product card, a header bar, a footer, a search box. These are called snippets, and you drop them into a template with {% include %}. Refer to a snippet by its name, in quotes.

{% include 'product-card' %}

Passing a value to the snippet

with makes a value available inside the snippet, named after the snippet itself:

{% include 'product-card' with featured_product %}

<!-- inside the snippet, {{ product-card }} now refers to featured_product -->

Repeating a snippet for every item in a list

for runs the snippet once per item in a collection:

{% include 'product-card' for collection.products %}

Passing extra named values

Any key:value pairs become variables inside the snippet:

{% include 'product-card' product:featured_product, layout:'wide' %}

6. Comments and raw output

comment

The body is stripped from the output:

{% comment %}
This is internal — never rendered.
{% endcomment %}

raw

Inside a raw block, {{ }} and {% %} are not parsed — they're emitted literally. Handy when you're outputting templates for another system:

<script type="text/x-handlebars">
{% raw %}
<p>Hello, {{ name }}!</p>
{% endraw %}
</script>

7. Filters

You've already seen the pipe syntax: {{ value | filter }}, {{ value | filter:arg }}, {{ value | first | second:'x' }}.

String

Filter

What it does

upcase

Uppercase the input

downcase

Lowercase the input

capitalize

First letter of each word uppercased

camelcase / camelize

Convert snake_case or kebab-case to camelCase

handle / handelize

Slugify (e.g. My Great Titlemy-great-title)

escape

HTML-escape <, >, &, "

strip_html

Remove all HTML tags

strip_newlines

Remove \n and \r

newline_to_br

Convert newlines to <br />

trim

Remove leading/trailing whitespace

append:'x'

Append a string

prepend:'x'

Prepend a string

replace:'a','b'

Replace every occurrence

replace_first:'a','b'

Replace only the first occurrence

remove:'x'

Remove every occurrence

remove_first:'x'

Remove only the first occurrence

truncate:n

Truncate to n characters

truncatewords:n

Truncate to n words

left:n / right:n

First / last n characters

split:','

Split a string on the given separator into a list

url_encode / url_decode

URL escaping

highlight_and_crop

Highlight a search term and crop the surrounding text

leading_html

First N characters of an HTML string, keeping tags closed

minifyHTML / minifyCSS / minifyJS

Minify a string

Numeric

Filter

Example

plus:n

{{ 5 | plus:3 }}8

minus:n

{{ 5 | minus:3 }}2

times:n

{{ 5 | times:3 }}15

divided_by:n

{{ 6 | divided_by:2 }}3

modulo:n

{{ 10 | modulo:3 }}1

round

Round to nearest integer

random

A random integer up to the input

Lists

Filter

What it does

size

Length of a list or a string. Also available as mylist.size

first

First item in the list

last

Last item in the list

join:','

Join the items into a single string with the given separator

combine

Merge two lists into one

Date / time

{{ article.published_at | date:'%b %d, %Y' }}      → Jan 14, 2026
{{ order.created_at | datetime_format }}
{{ event.starts_at | time_format }}

Filter

What it does

date:'mask'

Format a date using a custom mask (see specifiers below)

date_format

Auto-formatted date in your store's locale

time_format

Auto-formatted time in your store's locale

datetime_format

Auto-formatted date + time in your store's locale

Building a date mask

A mask is a quoted string where each %X placeholder is replaced with a part of the date. Everything else (spaces, commas, slashes, words) is kept exactly as written.

{{ article.published_at | date:'%B %d, %Y' }}        → January 14, 2026
{{ article.published_at | date:'%A, %b %d' }} → Wednesday, Jan 14
{{ event.starts_at | date:'%I:%M %p' }} → 09:30 AM
{{ event.starts_at | date:'%Y-%m-%d %H:%M' }} → 2026-01-14 09:30
{{ 'now' | date:'%c' }} → Wed Jan 14 09:30:00 2026

You can pass the string 'now' instead of a date variable to get the current time.

Specifiers

Day

Code

Meaning

Example

%a

Abbreviated weekday name

Sun

%A

Full weekday name

Sunday

%d

Day of the month, zero-padded

01..31

%j

Day of the year, zero-padded

001..366

%w

Day of the week as a number (Sunday = 0)

0..6

Month

Code

Meaning

Example

%b

Abbreviated month name

Jan

%B

Full month name

January

%m

Month as a number, zero-padded

01..12

Year

Code

Meaning

Example

%y

Two-digit year

26

%Y

Four-digit year

2026

%U

Week of the year, starting on Sunday

00..53

%W

Week of the year, starting on Monday

00..53

Time

Code

Meaning

Example

%H

Hour, 24-hour clock, zero-padded

00..23

%I

Hour, 12-hour clock, zero-padded

01..12

%M

Minute, zero-padded

00..59

%S

Second, zero-padded

00..59

%p

AM/PM indicator

AM, PM

%Z

Time zone name

Eastern Standard Time (EST)

Shortcuts and literals

Code

Meaning

%c

Default date + time (%a %b %e %T %Y)

%x

Default date only (%d/%m/%y)

%X

Default time only (%H:%M:%S)

%%

A literal % character

Money

These use your store's currency setting (General Settings → Preferences):

Filter

Output

money

$19.99

money_with_currency

$19.99 USD

money_without_currency

19.99

international_money / international_money_with_currency / international_money_without_currency

Same, formatted for international locales

weight_with_unit

1.5 kg

URLs and links

These automatically respect your site's domain, CDN settings, and theme directory:

Filter

What it does

asset_url

URL for a theme asset (image, JS, CSS)

global_asset_url

URL for a global (cross-site) asset

img_url

URL for a product/collection image

product_img_url / vendor_url

Convenience URL filters

url_for_product

Permalink builder for a product

link_to:'text'

<a href="…">text</a> from a URL

link_to_page

Link to a content page

link_to_tag / link_blog_tags

Tag link helpers

global_link

URL for built-in site actions (e.g. 'forgot-password', 'delete-cart-item')

HTML helpers

{{ 'styles.css' | asset_url | stylesheet_tag }}
{{ 'app.js' | asset_url | script_tag }}
{{ 'logo.png' | asset_url | img_tag:'My logo' }}

Filter

Output

img_tag

<img src="…" alt="…" />

script_tag

<script src="…"></script>

stylesheet_tag

<link rel="stylesheet" href="…" />

Forms

Filter

What it does

fieldvalue

Current value for a form field

default_field_display

Display a default-field setting

displayoption

Render a form option

display_error

Render the error message for a field

countryselect

A <select> of countries

stateselect

A <select> of states/provinces

pluralize:'singular','plural'

Pick the right word based on count

translatePresetSizes

Translate preset size names

Pagination

Filter

What it does

get_items_per_page

Returns the current page size

default_pagination

A ready-made pager block

foundation_pagination

Pager styled for the Foundation framework

Misc

Filter

What it does

json

Convert any value to a JSON string. Handy for passing data into inline JavaScript

size

Length of a string or list, or count of items in a record


8. Common patterns

Defaulting a value

{% if product.subtitle %}
<p>{{ product.subtitle }}</p>
{% else %}
<p>No description available.</p>
{% endif %}

Conditional CSS class

<li class="product {% if product.available %}in-stock{% else %}sold-out{% endif %}">
...
</li>

Reading a query string flag

{% if query.preview == 'true' %}
<div class="preview-banner">Preview mode</div>
{% endif %}

Loop with a separator (no trailing comma)

{% for tag in product.tags %}
{{ tag }}{% unless forloop.last %}, {% endunless %}
{% endfor %}

Pass data to inline JavaScript

<script>   var product = {{ product | json }}; </script>

Skip empty collections cleanly

{% if collection.products.size > 0 %}
{% for product in collection.products %}
...
{% endfor %}
{% else %}
<p>No products in this collection yet.</p>
{% endif %}

Paginated grid

{% paginate collection.products by 24 %}
<div class="grid">
{% for product in collection.products %}
{% include 'product-card' with product %}
{% endfor %}
</div>

{{ paginate | default_pagination }}
{% endpaginate %}

9. Theme-specific tags

In addition to the generic Fluid tags above, your theme has special tags for structural pieces of the page. These let you build the skeleton of a layout without having to know how the underlying page works:

Tag

Use

{% layout %}

Switch to a named layout

{% head %} ... {% endhead %}

Mark the <head> region (lets the application inject meta tags, scripts, SEO)

{% body %} ... {% endbody %}

Mark the <body> region

{% headcontent %}

Drop-in for additional <head> content

{% content %}

Outputs the current page's main content

{% section 'name' %}

Insert a named section

{% nav %}

Render the site navigation

{% sitemap %}

Render the sitemap

{% form %} ... {% endform %}

Open a managed form (CSRF, validation, submission)

{% slideshow %}

Render a slideshow widget

{% placeholder %}

Editable content placeholder

{% editcontent %} / {% editlinks %}

Inline editing markers (admin only)

{% templatesettings %}

Read template-level settings

{% iterate %}

Theme-flavoured iteration helper

{% items_per_page %}

Render the items-per-page selector

{% collection_sorter %}

Render the collection sort dropdown

{% page %}

Render a content page

{% put %}

Output theme-managed content

{% attr %}

Output a managed attribute

{% block %}

Define / output a named block

{% review %}

Render review widgets

{% global_include %}

Include a globally-managed snippet

Social embeds

Tag

What it embeds

{% facebook %}

Facebook share / like buttons

{% twitter %}

Twitter / X share button

{% linkedin %}

LinkedIn share button

{% youtube %}

YouTube video embed

{% googleplus %}

Google+ share button (legacy)

{% addthis %}

AddThis share bar

If you need the exact attribute list for any of these, check the example snippets in your theme — most themes ship with a working example of each.


10. Quick reference

<!-- output -->
{{ value }}
{{ value | filter }}
{{ value | filter:arg }}
{{ value | filter:arg1,arg2 | filter2 }}

<!-- logic -->
{% if x == y %}...{% elsif x == z %}...{% else %}...{% endif %}
{% unless x %}...{% endunless %}
{% case x %}{% when 'a' %}...{% when 'b' %}...{% else %}...{% endcase %}

<!-- loops -->
{% for item in items %}...{% endfor %}
{% for item in items limit:5 offset:10 reversed %}...{% endfor %}
{% for i in (1..10) %}...{% endfor %}
{% tablerow item in items cols:3 %}...{% endtablerow %}
{% cycle 'a', 'b', 'c' %}
{% paginate items by 20 %}...{% endpaginate %}

<!-- variables -->
{% assign x = 'hello' %}
{% capture x %}...{% endcapture %}
{% increment counter %}
{% decrement counter %}

<!-- includes -->
{% include 'partial' %}
{% include 'partial' with value %}
{% include 'partial' for collection %}
{% include 'partial' key:value %}

<!-- everything else -->
{% comment %}...{% endcomment %}
{% raw %}...{% endraw %}

11. Troubleshooting

Symptom

Likely cause

{{ foo }} prints nothing

The variable doesn't exist in this scope, or it's empty/false

Filter has no effect

Filter name typo, or the input type doesn't match (e.g. truncate on a non-string)

Syntax Error in 'for loop'

Wrong syntax — must be for item in collection

Syntax Error in 'assign'

Right-hand side is empty or undefined

Illegal template name from include

The snippet name has characters other than letters, digits, /, and _

Loop output looks doubled

A nested {% for %} is reusing the same loop variable name as the outer loop

{% raw %} inside {% capture %} didn't preserve {{ }}

capture still processes its contents — apply raw differently or escape manually

If a page renders blank or shows an error message instead of your content, the template hit an error. Check your most recent edit and confirm:

  • every {% if %} has a matching {% endif %} (same for for, unless, case, capture, paginate, tablerow, comment, raw),

  • filter arguments are quoted when they should be (replace:'a','b', not replace:a,b — unless a and b are variables you defined),

  • snippet names are in quotes inside include ({% include 'foo' %}, not {% include foo %}).

Did this answer your question?