Liquid is a template language used to create dynamic content. There are two types of markup in Liquid: Output and Tag.
{{ matched pairs of curly brackets (ie, braces) }}
{% matched pairs of curly brackets and percent signs %}
An output statement is a set of double curly braces containing an expression; when the template is rendered, it gets replaced with the value of that expression.
Here is a simple example of output:
1Hello {{name}}2Hello {{user.name}}3Hello {{ 'tobi' }}
Expressions are statements that have values. Liquid templates can use expressions in several places; most often in output statements, but also as arguments to some tags or filters.
Liquid accepts the following kinds of expressions:
var_name
, not $var_name
).my_variable[<KEY EXPRESSION>]
— The name of the variable, followed immediately by square brackets containing a key expression.
my_hash.key
— Hashes also allow a shorter "dot" notation, where the name of the variable is followed by a period and the name of a key. This only works with keys that don't contain spaces, and (unlike the square bracket notation) does not allow the use of a key name stored in a variable.site.posts[34].title
.).first
or .last
to resolve to its first or last element..size
to resolve to the number of elements in the original expression, as an integer."my string"
or 'my string'
). There is no difference; neither style allows variable interpolation.true
, false
, and nil
.Note that there is no way to write a literal array or hash as an expression; arrays and hashes must be passed into the template, or constructed obliquely with a tag or output statement.
Output markup can take filters, which modify the result of the output statement. You can invoke filters by following the output statement's main expression with:
|
):
) and a comma-separated list of additional parameters to the filter. Each additional parameter must be a valid expression, and each filter pre-defines the parameters it accepts and the order in which they must be passed.Filters can also be chained together by adding additional filter statements (starting with another pipe character). The output of the previous filter will be the input for the next one.
1Hello {{ 'tobi' | upcase }}2Hello tobi has {{ 'tobi' | size }} letters!3Hello {{ '*tobi*'| upcase }}4Hello {{ 'now' | date: "%Y %h" }}
Under the hood, a filter is a method that takes one or more parameters and returns a value. Parameters are passed to filters by position: the first parameter is the expression preceding the pipe character, and additional parameters can be passed using the name: arg1, arg2
syntax described above.
append
- append a string, e.g. {{ 'foo' | append:'bar' }} #=> 'foobar'
capitalize
- capitalize words in the input sentenceceil
- rounds a number up to the nearest integer, e.g. {{ 4.6 | ceil }} #=> 5
date
- reformat a date (syntax reference)default
- returns the given variable unless it is null or the empty string, when it will return the given value, e.g. {{ undefined_variable | default: "Default value" }} #=> "Default value"
divided_by
- integer division, e.g. For integer output {{ 10 | divided_by:3 }} #=> 3
For float output {{ 10 | divided_by:3.0 }} #=> 3.333333333335
. It is advisable to use round
function when using float.downcase
- convert an input string to lowercaseescape_once
- returns an escaped version of HTML without affecting existing escaped entitiesescape
- HTML escape a stringfirst
- get the first element of the passed in arrayfloor
- rounds a number down to the nearest integer, e.g. {{ 4.6 | floor }} #=> 4
join
- join elements of the array with certain character between themlast
- get the last element of the passed in arraylstrip
- strips all whitespace from the beginning of a stringmap
- map/collect an array on a given propertyminus
- subtraction, e.g. {{ 4 | minus:2 }} #=> 2
modulo
- remainder, e.g. {{ 3 | modulo:2 }} #=> 1
newline_to_br
- replace each newline (\n) with HTML breakplus
- addition, e.g. {{ '1' | plus:'1' }} #=> 2
, {{ 1 | plus:1 }} #=> 2
prepend
- prepend a string, e.g. {{ 'bar' | prepend:'foo' }} #=> 'foobar'
remove_first
- remove the first occurrence, e.g. {{ 'barbar' | remove_first:'bar' }} #=> 'bar'
remove
- remove each occurrence, e.g. {{ 'foobarfoobar' | remove:'foo' }} #=> 'barbar'
replace_first
- replace the first occurrence e.g. {{ 'barbar' | replace_first:'bar','foo' }} #=> 'foobar'
replace
- replace each occurrence, e.g. {{ 'foofoo' | replace:'foo','bar' }} #=> 'barbar'
reverse
- reverses the passed in array. To reverse text, convert to an array first: {{ 'foobar' | split: "" | reverse | join: ""}} => 'raboof'
round
- rounds input to the nearest integer or specified number of decimals, e.g. {{ 4.5612 | round: 2 }} #=> 4.56
rstrip
- strips all whitespace from the end of a stringsize
- return the size of an array or stringslice
- slice a string. Takes an offset and length, e.g. {{ "hello" | slice: -3, 3 }} #=> llo
sort
- sort elements of the arraysplit
- split a string on a matching pattern, e.g. {{ "a~b" | split:"~" }} #=> ['a','b']
strip_html
- strip HTML from stringstrip_newlines
- strip all newlines (\n) from stringstrip
- strips all whitespace from both ends of the stringtimes
- multiplication, e.g {{ 5 | times:4 }} #=> 20
to_json
- convert Liquid objects to valid JSON, e.g. {{ flow.data | to_json }} #=> {"foo":"bar","baz":"bat"}
truncate
- truncate a string down to x characters. It also accepts a second parameter that will append to the string (defaults to "..."), e.g. {{ 'foobarfoobar' | truncate: 5, '.' }} #=> 'foob.'
truncatewords
- truncate a string down to x words. It also accepts a second parameter that will append to the string (defaults to "..."), e.g. {{ 'foo bar' | truncatewords: 1, '...' }} #=> 'foo...'
uniq
- removed duplicate elements from an array, optionally using a given property to test for uniquenessupcase
- convert an input string to uppercaseurl_encode
- URL encode a stringurl_decode
- decodes a string that has been encoded as a URL or by url_encodeRaw temporarily disables tag processing. This is useful for generating content (e.g., Mustache, Handlebars) which uses conflicting syntax.
1{% raw %}2In Handlebars, {{ this }} will be HTML-escaped, but {{{ that }}} will not.3{% endraw %}
if / else
statements should be familiar from other programming languages. Liquid implements them with the following tags:
{% if <CONDITION> %} ... {% endif %}
— Encloses a section of template which will only be run if the condition is true.{% elsif <CONDITION> %}
— Can optionally be used within an if ... endif
block. Specifies another condition; if the initial "if" fails, Liquid tries the elsif
, and runs the following section of template if it succeeds. You can use any number of elsif
s in an if
block.{% else %}
— Can optionally be used within an if ... endif
block, after any elsif
tags. If all preceding conditions fail, Liquid will run the section of template following the "else" tag.{% unless <CONDITION> %} ... {% endunless %}
— The reverse of an "if" statement. Don't use elsif
or "else" with an unless statement.The condition of an if
, elsif
or unless
tag should be either a normal Liquid expression or a comparison using Liquid expressions. Note that the comparison operators are implemented by the "if"-like tags; they don't work anywhere else in Liquid.
The available comparison operators are:
==, !=,
and <>
— equality and inequality (the latter two are synonyms)
empty
(unquoted) that you can compare arrays to; the comparison is true if the array has no members.<, <=, >, >=
— less/greater-thancontains
— a wrapper around Ruby's include?
method, which is implemented on strings, arrays, and hashes. If the left argument is a string and the right isn't, it stringifies the right.The available Boolean operators are:
and
or
Note that there is NO "not" operator. Also note that you CANNOT use parentheses to control order of operations, and the precedence of the operators appears to be unspecified. So when in doubt, use nested "if" statements instead of risking it.
Liquid expressions are tested for "truthiness" in what looks like a Ruby-like way:
true
is true.false
is false.1{% if user %}2Hello {{ user.name }}3{% endif %}
1# Same as above2{% if user != null %}3Hello {{ user.name }}4{% endif %}
1{% if user.name == 'tobi' %}2Hello tobi3{% elsif user.name == 'bob' %}4Hello bob5{% endif %}
1{% if user.name == 'tobi' or user.name == 'bob' %}2Hello tobi or bob3{% endif %}
1{% if user.name == 'bob' and user.age > 45 %}2Hello old bob3{% endif %}
1{% if user.name != 'tobi' %}2Hello non-tobi3{% endif %}
1# Same as above2{% unless user.name == 'tobi' %}3Hello non-tobi4{% endunless %}
1# Check for the size of an array2{% if user.payments == empty %}3you never paid !4{% endif %}56{% if user.payments.size > 0 %}7you paid !8{% endif %}
1{% if user.age > 18 %}2Login here3{% else %}4Sorry, you are too young5{% endif %}
1# array = 1,2,32{% if array contains 2 %}3array includes 24{% endif %}
1# string = 'hello world'2{% if string contains 'hello' %}3string includes 'hello'4{% endif %}
If you need more conditions, you can use the case
statement:
1{% case condition %}2{% when 1 %}3hit 14{% when 2 or 3 %}5hit 2 or 36{% else %}7... else ...8{% endcase %}
Example:
1{% case template %}23{% when 'label' %}4// {{ label.title }}5{% when 'product' %}6// {{ product.vendor }} / {{ product.title }}7{% else %}8// {{page_title}}9{% endcase %}
Often you have to alternate between different colors or similar tasks. Liquid
has built-in support for such operations, using the cycle
tag.
1{% cycle 'one', 'two', 'three' %}2{% cycle 'one', 'two', 'three' %}3{% cycle 'one', 'two', 'three' %}4{% cycle 'one', 'two', 'three' %}
will result in
1one2two3three4one
If no name is supplied for the cycle group, then it's assumed that multiple calls with the same parameters are one group.
If you want to have total control over cycle groups, you can optionally specify the name of the group. This can even be a variable.
1{% cycle 'group 1': 'one', 'two', 'three' %}2{% cycle 'group 1': 'one', 'two', 'three' %}3{% cycle 'group 2': 'one', 'two', 'three' %}4{% cycle 'group 2': 'one', 'two', 'three' %}
will result in
1one2two3one4two
Liquid allows for
loops over collections:
1{% for item in array %}2{{ item }}3{% endfor %}
For loops can iterate over arrays, hashes, and ranges of integers.
When iterating a hash, item[0]
contains the key, and item[1]
contains the value:
1{% for item in hash %}2{{ item[0] }}: {{ item[1] }}3{% endfor %}
Instead of looping over an existing collection, you can also loop through a range of numbers. Ranges look like (1..10)
— parentheses containing a start value, two periods, and an end value. The start and end values must be integers or expressions that resolve to integers.
1# if item.quantity is 4...2{% for i in (1..item.quantity) %}3{{ i }}4{% endfor %}5# results in 1,2,3,4
You can exit a loop early with the following tags:
{% continue %}
— immediately end the current iteration, and continue the "for" loop with the next value.{% break %}
— immediately end the current iteration, then completely end the "for" loop.Both of these are only useful when combined with something like an "if" statement.
1{% for page in pages %}2# Skip anything in the hidden_pages array, but keep looping over the rest of the values3{% if hidden_pages contains page.url %}4{% continue %}5{% endif %}6# If it's not hidden, print it.7* [page.title](page.url)8{% endfor %}
1{% for page in pages %}2* [page.title](page.url)3# After we reach the "cutoff" page, stop the list and get on with whatever's after the "for" loop:4{% if cutoff_page == page.url %}5{% break %}6{% endif %}7{% endfor %}
During every for
loop, the following helper variables are available for extra
styling needs:
1forloop.length # => length of the entire for loop2forloop.index # => index of the current iteration3forloop.index0 # => index of the current iteration (zero based)4forloop.rindex # => how many items are still left?5forloop.rindex0 # => how many items are still left? (zero based)6forloop.first # => is this the first iteration?7forloop.last # => is this the last iteration?
There are several optional arguments to the for
tag that can influence which items you receive in
your loop and what order they appear in:
limit:<INTEGER>
lets you restrict how many items you get.offset:<INTEGER>
lets you start the collection with the nth item.reversed
iterates over the collection from last to first.Restricting elements:
1# array = [1,2,3,4,5,6]2{% for item in array limit:2 offset:2 %}3{{ item }}4{% endfor %}5# results in 3,4
Reversing the loop:
{% for item in collection reversed %} {{item}} {% endfor %}
A for loop can take an optional else
clause to display a block of text when there are no items in the collection:
1# items => []2{% for item in items %}3{{ item.title }}4{% else %}5There are no items!6{% endfor %}
You can store data in your own variables, to be used in output or other tags as
desired. The simplest way to create a variable is with the assign
tag, which
has a pretty straightforward syntax:
1{% assign name = 'freestyle' %}23{% for t in collections.tags %}{% if t == name %}4<p>Freestyle!</p>5{% endif %}{% endfor %}
Variables that are created using the assign
tag only exist within the widget where they are created.
To create or modify variables that are accessible throughout your flow via other widgets under the key {{flow.variables.<key>}}
, use the Set Variables widget instead.
Another way of doing this would be to assign true / false
values to the
variable:
1{% assign freestyle = false %}23{% for t in collections.tags %}{% if t == 'freestyle' %}4{% assign freestyle = true %}5{% endif %}{% endfor %}67{% if freestyle %}8<p>Freestyle!</p>9{% endif %}
If you want to combine a number of strings into a single string and save it to
a variable, you can do that with the capture
tag. This tag is a block which
"captures" whatever is rendered inside it, then assigns the captured value to
the given variable instead of rendering it to the screen.
1{% capture attribute_name %}{{ item.title | handleize }}-{{ i }}-color{% endcapture %}23<label for="{{ attribute_name }}">Color:</label>4<select name="attributes[{{ attribute_name }}]" id="{{ attribute_name }}">5<option value="red">Red</option>6<option value="green">Green</option>7<option value="blue">Blue</option>8</select>
Dates can be formatted with a variety of options.
The input to the date filter must be one the following, specific values in order to avoid errors:
'now'
, which will be interpreted as the current timeyyyy-MM-dd HH:mm:ss
EEE MMM dd hh:mm:ss yyyy
For example, any of these inputs are valid:
1# Format the current time using the 'now' keyword2Hello {{ 'now' | date: "%Y %d" }}34# Format a given time, input as the number of seconds5Hello {{ 1623959503 | date: "%Y %d" }}67# Format a given time, input as a string in "yyyy-MM-dd HH:mm:ss" format8Hello {{ '2021-06-17 12:51:43' | date: "%Y %d" }}910# Format a given time, input as a string in "EEE MMM dd hh:mm:ss yyyy" format11Hello {{ 'Thu Jun 17 12:51:43 2021' | date: "%Y %d" }}
When using the now
keyword instead of a specific date, the resulting time will be output in US Pacific Time, not UTC.
The following format tokens are accepted for formatting your date:
1%a - The abbreviated weekday name ("Sun")2%A - The full weekday name ("Sunday")3%b - The abbreviated month name ("Jan")4%B - The full month name ("January")5%c - The preferred local date and time representation6%d - Day of the month (01..31)7%e - Day of the month (1..31)8%H - Hour of the day, 24-hour clock (00..23)9%I - Hour of the day, 12-hour clock (01..12)10%j - Day of the year (001..366)11%k - Hour of the day, 24-hour clock (0..23)12%l - Hour of the day, 12-hour clock (0..12)13%m - Month of the year (01..12)14%M - Minute of the hour (00..59)15%p - Meridian indicator ("AM" or "PM")16%S - Second of the minute (00..60)17%U - Week number of the current year,18starting with the first Sunday as the first19day of the first week (00..53)20%W - Week number of the current year,21starting with the first Monday as the first22day of the first week (00..53)23%w - Day of the week (Sunday is 0, 0..6)24%x - Preferred representation for the date alone, no time25%X - Preferred representation for the time alone, no date26%y - Year without a century (00..99)27%Y - Year with century28%Z - Time zone name29%% - Literal "%" character
In Liquid, you can include a hyphen in your tag syntax {{-
, -}}
, {%-
, and -%}
to strip whitespace from the left or right side of a rendered tag.
If you don't want any of your tags to print whitespace, as a general rule you can add hyphens to both sides of all your tags ({%-
and -%}
).
The following example ensures whitespace doesn't get concatenated to the integer as it increments:
1{%- if flow.variables.count -%}2{{flow.variables.count | plus: 1}}3{%- else -%}405{%- endif -%}
A rendered string has a maximum length of 16,384 characters.
In a Studio Flow, a liquid variable containing a value greater than 16,384 characters means that the value is not rendered in the output. The liquid variable, {{some_var}}
for example, is rendered in the output instead.
Comments
Any content that you put between
{% comment %}
and{% endcomment %}
tags is turned into a comment.