Title: Liquid Templating Crash Course
Subheading: My Liquid templating crash course, including Jekyll- and GitHub Pages-specific details.
Tags: Jekyll
This is mostly here as a reference to remind myself of how this works on the
rare occassions when I need to work on some Liquid templates and need to
re-learn this. But it might be useful to someone else as well.
[Liquid](https://github.com/Shopify/liquid) is a template language that was
first developed for and is still used by Shopify, but it's now open source
and also used by Jekyll and many other projects.
You can use Liquid templating in your Jekyll site's layouts and includes, but
you can also use it directly in posts or pages, which can be pretty handy
(for example it's used loads in [the source code for this page](https://raw.githubusercontent.com/seanh/seanh.github.io/f3e4beaebdec52d9889e2c1901224dd362e366c5/_posts/2019-09-29-liquid.md)).
Liquid is a bit of a weird template language. At least it is if you have a
similar background to me: used to Python-world templating languages like
Jinja2. As a result if you try to just use Liquid without taking the time out
to properly learn it first, you'll be frustrated.
The good news is that Liquid is a pretty small language, and more-or-less
well-documented, so it is thoroughly learnable in an afternoon.
Weird Things about Liquid
-------------------------
Here's a few of the unexpected things that threw me, before I took the time to
learn Liquid properly:
* [Shopify's Liquid docs](https://help.shopify.com/en/themes/liquid) contain a
lot of Shopify-only stuff, and those are what often comes up in search results,
which is confusing.
To learn how to use Liquid effectively with GitHub Pages / Jekyll you need to read
the [Liquid docs](https://shopify.github.io/liquid/),
[Jekyll's docs](https://jekyllrb.com/docs/)
and
[Liquid for Designers](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers).
* You can't initialize your own arrays or hashes directly.
You can create new variables in Liquid templates and assign string, number,
boolean or `nil` values to them. For example: `{% assign my_string = "foo" %}`.
But there are no array or hash literals and no way to
initialize a new array or hash.
There are oblique workarounds like splitting a string to create a new array of strings,
or using various array processing filters to create new arrays,
or using Jekyll's front matter or data files to create your own arrays or hashes.
* There's no "not" (or `!`) operator. You do have `unless` (which is the
opposite of `if`) and `else`, and `!=`, but there's no `not` / `!`.
* Truthiness doesn't work how you expect it to: only `false` and `nil` are
falsy, everything else is truthy including empty strings, empty arrays, and
`0`. If your if-statement doesn't seem to be working this is probably why.
* The [order of operations](https://shopify.github.io/liquid/basics/operators/#order-of-operations)
in compound conditionals is fixed and you can't use `( ... )`'s to control it.
`(...)`'s aren't allowed in conditional expressions.
(You can choose to use nested ifs instead of compound ifs, though).
* An explanation of hashes and how to use them is mysteriously missing from the
documentation, which doesn't even list hashes as one of the available types.
See [my section on hashes](#hashes) instead.
* [Jekyll's `site.categories` and `site.tags`](#jekylls-sitecategories-and-sitetags)
are a bit difficult to work with.
Objects, Tags and Filters
-------------------------
There are only three types of thing in a Liquid template: objects (variables
that have types), tags (control flow, iteration, and variable assignment), and
filters (functions that return new objects).
### 1. Objects, a.k.a Variables: `{{ ... }}`
These are references to variables, or sub-parts of variables in the case of
hash or array access, that print out the referenced variable's value.
All object references print something out into the rendered page, unless the
variable's value is `nil` or the empty string.
For example given a variable `foo` whose value is `"bar"` (a string),
{% assign foo = "bar" %} this:
```liquid
{{ foo }}
```
will output this:
```html
bar
```
Each variable has a **type**. There are 6 types in Liquid:
1. [Strings](#strings): `"..."` or `'...'` (there's no difference between double and single quotes)
2. [Numbers](#numbers): `42` or `25.672`
3. Booleans: `true` and `false`
4. `nil`
5. [Arrays](#arrays): lists of objects.
The special object `empty` is shorthand for an empty array.
For example `{% if my_array == empty %}` is equivalent to
`{% if my_array.size == 0 %}`
6. [Hashes](#hashes): objects with multiple fields
### 2. Tags: `{% ... %}`
Tags implement **control flow**, **iteration** and **variable assignments**,
among other things.
For example, `{% if %}` is one of the control flow tags.
This if-statement will print out the value of the variable `user` _if_ a
variable named user exists and its value isn't `false` or `nil`:
```liquid
{% if user -%}
The user is: {{ user }}
{%- else -%}
There is no user.
{%- endif %}
```
Whitespace Control with {%- and -%}
The `-` characters in `{%-` and `-%}` are
[whitespace control](https://shopify.github.io/liquid/basics/whitespace/).
A `{%-` strips any leading whitespace from before the tag's contents, in the rendered output.
It strips spaces, tabs and newlines.
`-%}` strips any trailing whitespace after the tag's contents.
They remove empty lines where the tags were from the rendered output.
They can also remove the indentation (leading spaces) when the contents of tags are indented in the source file,
but this only works when the tag only contains a single line, indentation from subsequent indented lines doesn't
seem to be stripped.
When there's no `user` variable, or `user`'s value is `false` or `nil`, the `{% if %}` tag above outputs:
```html
There is no user.
```
When there _is_ a `user` variable whose value is `"fred"` it outputs:
{%- assign user = "fred" %}
```html
The user is: fred
```
### 3. Filters: `|`
Filters are functions that return new objects based on a given object.
For example: doing string manipulations and returning new strings;
doing array operations and returning new arrays; doing math and returning new numbers; formatting dates; and more.
Filters are pure functions that always just compute and return a new object,
they never modify a given object and never have any side effects.
(But you can use [`{% assign %}`](#variable-assignment-tags--assign---capture---increment--and--decrement-)
to assign the new object returned by a filter to an existing variable name.)
[`upcase`](https://shopify.github.io/liquid/filters/upcase/) is a simple string
manipulation filter that returns an ALL CAPS copy of a given string. This:
```liquid
{{ "apple" | upcase }}
```
Outputs:
```
APPLE
```
[`append`](https://shopify.github.io/liquid/filters/append/) is another string
manipulation filter that takes an additional parameter: the suffix string to
append to the first string. This:
```liquid
The file is: {{ "index" | append: ".html" }}
```
Outputs:
```html
The file is: index.html
```
Filters can take multiple positional parameters, but there are no named parameters:
```liquid
{{ "A long string to be truncated" | truncate: 24, ", and so on" }}
```
Outputs:
```html
A long string, and so on
```
Filter parameters can be optional. For example [`truncate`](https://shopify.github.io/liquid/filters/truncate/)'s
second argument defaults to an ellipsis if omitted:
```liquid
{{ "A long string to be truncated" | truncate: 24 }}
```
Outputs:
```html
A long string to be t...
```
Finally, multiple filters can be chained together in a sequence like UNIX
pipes:
```liquid
{{ "adam!" | capitalize | prepend: "Hello " }}
```
Strings
-------
String literals can use either `'single quotes'` or `"double quotes"`, there's no difference.
There are lots of string manipulation filters:
See also:
The complete list of all filters in the [Liquid docs](https://shopify.github.io/liquid/).
[` | capitalize`](https://shopify.github.io/liquid/filters/capitalize/)
: a copy of a string with only the first character capitalized
[` | downcase`](https://shopify.github.io/liquid/filters/downcase/)
: an all-lowercase copy of a string
[` | upcase`](https://shopify.github.io/liquid/filters/upcase/)
: a copy of a string in ALL CAPS
[` | lstrip`](https://shopify.github.io/liquid/filters/lstrip/)
: a copy of a string with leading whitespace removed
[` | rstrip`](https://shopify.github.io/liquid/filters/rstrip/)
: a copy of a string with trailing whitespace removed
[` | strip`](https://shopify.github.io/liquid/filters/strip/)
: a copy of a string with leading and trailing whitespace removed
[` | strip_html`](https://shopify.github.io/liquid/filters/strip_html/)
: a copy of a string with HTML tags removed
[` | strip_newlines`](https://shopify.github.io/liquid/filters/strip_newlines/)
: a copy of a string with line breaks removed
[` | remove: "rain"`](https://shopify.github.io/liquid/filters/remove/)
: a copy of string with all occurrences of a substring removed
[` | remove_first: "rain"`](https://shopify.github.io/liquid/filters/remove_first/)
: a copy of a string with the first occurrence of a substring removed
[` | replace: "my", "your"`](https://shopify.github.io/liquid/filters/replace/)
: a copy of a string with all occurrences of the first argument replaced with the second argument
[` | replace_first: "my", "your"`](https://shopify.github.io/liquid/filters/replace_first/)
: a copy of a string with the first occurrence of the first argument replaced with the second argument
[` | newline_to_br`](https://shopify.github.io/liquid/filters/newline_to_br/)
: a copy of a string with all the `\n`'s replaced with ` `'s
[` | escape`](https://shopify.github.io/liquid/filters/escape/)
: a URL-escaped copy of a string
[` | escape_once`](https://shopify.github.io/liquid/filters/escape_once/)
: like `escape` but avoids double-escaping
[` | url_encode`](https://shopify.github.io/liquid/filters/url_encode/)
: a copy of a string with any URL-unsafe characters percent-encoded
[` | url_decode`](https://shopify.github.io/liquid/filters/url_decode/)
: a copy of a string with any percent-encoded characters decoded
[` | append: ".html"`](https://shopify.github.io/liquid/filters/append/)
: a new string by concatenating two strings
[` | prepend: "prefix"`](https://shopify.github.io/liquid/filters/prepend/)
: a copy of a string with a prefix string prepended onto the front
[` | size`](https://shopify.github.io/liquid/filters/size/)
: the number of characters in a string. Also works with arrays
[` | slice: 0`](https://shopify.github.io/liquid/filters/slice/)
[` | slice: 2, 5`](https://shopify.github.io/liquid/filters/slice/)
[` | slice: -3, 2`](https://shopify.github.io/liquid/filters/slice/)
: a substring of a string
[` | truncate: 25`](https://shopify.github.io/liquid/filters/truncate/)
[` | truncate: 25, ", and so on"`](https://shopify.github.io/liquid/filters/truncate/)
: a copy of a string truncated to the given number of characters, with an
ellipsis appended if any characters had to be removed from the end.
If the second argument is given it's used instead of an ellipsis.
Use `""` for the second argument if you just don't want any ellipsis.
[` | truncatewords: 3`](https://shopify.github.io/liquid/filters/truncatewords/)
[` | truncatewords: 3, "--"`](https://shopify.github.io/liquid/filters/truncatewords/)
: a copy of a string truncated to the given number of words.
[` | split: ", "`](https://shopify.github.io/liquid/filters/split/)
: an array of substrings by splitting a string on the given substring.
You can also join an array of substrings into a single string with the
`|join` filter, which works on arrays
There's no filter for reversing a string, but you can do it by first splitting
it into a character array, then reversing the array, then joining the array
back into a string: `{{ | split: "" | reverse | join: "" }}`
[` | date: ""`](https://shopify.github.io/liquid/filters/date/)
: a copy of a timestamp string, formatted according to the given [strftime](http://strftime.net/) format string.
Jekyll also [adds a lot more date formatting filters](https://jekyllrb.com/docs/liquid/filters/)
Numbers
-------
Number literals can be either integers: `25`, or floats: `39.756`.
There are lots of filters for manipulating numbers:
See also:
The complete list of all filters in the [Liquid docs](https://shopify.github.io/liquid/).
[` | abs`](https://shopify.github.io/liquid/filters/abs/)
: the absolute value of a number
[` | round`](https://shopify.github.io/liquid/filters/round/)
[` | round: 2`](https://shopify.github.io/liquid/filters/round/)
: a copy of a number, rounded to the nearest integer or to a given number of decimal places
[` | at_least: 5`](https://shopify.github.io/liquid/filters/at_least/)
: a copy of a number, clamped to a given minimum value
[` | at_most: 5`](https://shopify.github.io/liquid/filters/at_most/)
: a copy of a number, clamped to a given maximum value
[` | floor`](https://shopify.github.io/liquid/filters/floor/)
: a copy of a number, rounded down to the nearest whole number
[` | ceil`](https://shopify.github.io/liquid/filters/ceil/)
: a copy of a number, rounded up to the nearest whole number
[` | plus: 2`](https://shopify.github.io/liquid/filters/plus/)
: a copy of a number by adding a given number to it
[` | minus: 2`](https://shopify.github.io/liquid/filters/minus/)
: a copy of a number by subtracting a given number from it
[` | times: 2`](https://shopify.github.io/liquid/filters/times/)
: a copy of a number by multiplying it by a given number
[` | divided_by: 4`](https://shopify.github.io/liquid/filters/divided_by/)
: a copy of a number by dividing it by a given number
[` | modulo: 2`](https://shopify.github.io/liquid/filters/modulo/)
: a new number by taking the remainder from dividing a number by a given number
Arrays
------
See also:
[Liquid's docs on arrays.](https://shopify.github.io/liquid/basics/types/#array)
[Jekyll passes various arrays to your templates as variables](https://jekyllrb.com/docs/variables/#global-variables),
for example:
* `site.pages` is an array of all your site's pages
* `site.posts` is an array of all your site's posts in reverse chronoligical order
* `page.categories` is a list of all this page's categories, and `page.tags` is a list of all this page's tags
Individual items in an array can be accessed with `[]`'s, for example:
* `{{ site.posts[0] }}` is the newest post
* `{{ site.posts[-1] }}` is the oldest post
Arrays also have convenience attributes `first` and `last`, for example you
could print the titles of the site's first and last posts like this:
* `{{ site.posts.first.title }}` is the newest post
* `{{ site.posts.last.title }}` is the oldest post
You can loop over an array with a [for loop](#iteration-tags-for-cycle-and-tablerow):
```liquid
{% for post in site.posts %}
{{ post.title }}
{% endfor %}
```
Each array has a `.size` field that's the number of items in the array.
`empty` is a special object that's equal to an empty array. For example this:
```liquid
{% if site.pages == empty %}
This site has no pages.
{% endif %}
```
is equivalent to this:
```liquid
{% if site.pages.size == 0 %}
This site has no pages.
{% endif %}
```
You can't initialize your own arrays directly, but there are a few workarounds:
* You can use the [`split` filter](https://shopify.github.io/liquid/filters/split/)
to split a string into an array of substrings:
`{% assign my_array = "First String, Second String, Third String" | split: ", " %}`
* You can use [ranges](#looping-over-a-range-of-numbers) to create arrays of numbers for for loops.
For example this:
#!liquid
The 5 Most Recent Posts
{%- for i in (0..4) %}
{{ site.posts[i].title }}
{%- endfor %}
prints a list of the title's of the site's 5 most recent posts:
#!html
The 5 Most Recent Posts
Compiling Python Requirements Files with GNU make
How to Publish a Python Package from GitHub Actions
How I Use Restic to Back up My Home Folders to Backblaze B2
Mutt
How to Backup Your Fastmail & Gmail Accounts with isync
* You can initialize an array from an existing array or arrays,
by using one of several filters that return new arrays from existing ones,
such as `concat`, `reverse`, `sort`, `map`, and `where` (see below for the complete list).
For example: `{% assign draft_posts = site.posts | where: "draft", true %}`
* You can define an array in the [YAML front matter](https://jekyllrb.com/docs/front-matter/)
at the top of your file. This is a Jekyll feature, not a Liquid feature. For example:
#!liquid
---
my_array: [6, five, 3.2]
---
`my_array` becomes available as an attribute of the `page` variable.
For example this:
#!liquid
{% for item in page.my_array %}
{{ item }}
{%- endfor %}
Outputs:
6
five
3.2
An array in the layout's front matter (available on the `layout` variable)
or in `_config.yml` (available on the `site` variable) would also work.
* [Jekyll's data files](https://jekyllrb.com/docs/datafiles/)
would be another way to create your own arrays.
There are lots of filters for manipulating arrays:
See also:
The complete list of all filters in the [Liquid docs](https://shopify.github.io/liquid/).
[` | size`](https://shopify.github.io/liquid/filters/size/)
: the number of items in an array. Also works with strings. Arrays also have an `.size` attribute
[` | compact`](https://shopify.github.io/liquid/filters/compact/)
: a copy of an array with any `nil`'s removed
[` | concat: other_array`](https://shopify.github.io/liquid/filters/concat/)
: a new array by concatenating an array witn another array (to join more than two arrays pipe multiple `|concat`'s together)
[` | first`](https://shopify.github.io/liquid/filters/first/)
: the first item of an array. Arrays also have an `.first` attribute
[` | last`](https://shopify.github.io/liquid/filters/last/)
: the last item of an array. Arrays also have an `.last` attribute
[` | join: " and "`](https://shopify.github.io/liquid/filters/join/)
: a string by joining an array of strings into a single string
[` | reverse`](https://shopify.github.io/liquid/filters/reverse/)
: a copy of an array with the order of items reversed
[` | sort`](https://shopify.github.io/liquid/filters/sort/)
[` | sort: "field"`](https://shopify.github.io/liquid/filters/sort/)
: a copy of an array sorted case-sensitively
**Todo**: It looks like Jekyll might replace `sort` with its own custom one that
has an additional "nils order" parameter.
See:
[` | sort_natural`](https://shopify.github.io/liquid/filters/sort_natural/)
[` | sort_natural: "field"`](https://shopify.github.io/liquid/filters/sort_natural/)
: a copy of an array sorted case-insensitively
[` | map: "foo"`](https://shopify.github.io/liquid/filters/map/)
: an array of the `"foo"` attribute values from each object in the original array
[` | uniq`](https://shopify.github.io/liquid/filters/uniq/)
: a copy of an array with duplicate items removed
[` | where: "available"`](https://shopify.github.io/liquid/filters/where/)
[` | where: "type", "kitchen"`](https://shopify.github.io/liquid/filters/where/)
: a filtered copy of an array containing only those objects from the original array where the value of
a named property (the first argument) matches a given value (the second
argument). The second argument defaults to "truthy"
**Todo**: I think Jekyll overrides Liquid's built-in `where` with its own different
version, and it also adds `where_exp`, `group_by` and `group_by_exp`.
See:
Hashes
------
They're missing from the documentation, but Liquid also has hashes:
objects with multiple named fields.
[Jekyll passes several hashes into your templates](https://jekyllrb.com/docs/variables/),
for example:
* `site` is an object containing site-wide information and settings from your `_config.yml` file
* `page` is an object containing page-specific information and metadata from the page's YAML front matter
* `layout` is an object containing layout-specific information and metadata from the layout's YAML front matter
* `site.pages` is an array of objects, one for each page on your site
* `site.posts` is an array of objects, one for each post on your site
* On GitHub Pages `site.github` is an object containing all sorts of GitHub Pages-specific metadata about the site
The main thing you can do with hashes is access their attributes using `.` or `[]` notation.
For example here's some code that prints out some common Jekyll site attributes:
```liquid
Site title: {{ site.title }}
Site description: {{ site.description }}
Site base URL: {{ site.baseurl }}
Site last updated at: {{ site.time }}
```
This outputs:
```
Site title: seanh.cc
Site description:
Site base URL:
Site last updated at: 2023-08-05 13:59:13 +0000
```
You can also access object attributes using `["string"]` notation, which works
even if the key has a space in it. For example here's some code that prints
out a bunch of Jekyll's page attributes:
```liquid
Page title: {{ page["title"] }}
Beginning of page content: {{ page["content"] | truncate }}
Page URL: {{ page["url"] }}
```
This outputs:
```
Page title: Liquid Templating Crash Course
Beginning of page content: This is mostly here as a reference to remind my...
Page URL: /2019/09/29/liquid/
```
You can also put a string variable or an expression that resolves to a string
in `[]`'s, for example:
```liquid
{% assign tag = "Jekyll" %}
Number of posts tagged "{{ tag }}": {{ site.tags[tag].size }}
```
You can loop over a hash with `{% for %}` and it loops over the
hash's keys:
```liquid
{% for key in site.github limit:3 -%}
{{ key }}: {{ site.github[key] }}
{% endfor %}
```
Output:
```
api_url: https://api.github.com
archived: false
baseurl:
```
Trying to access an attribute that doesn't exist returns `nil`, which is
falsey. For example this page has an attribute `does_exist: true` in its Jekyll
front matter, but it _doesn't_ have any attribute named `doesnt_exist`. Given
this code:
```liquid
{% if page.does_exist -%}
The does_exist attribute does exist
{%- endif %}
{% if page.doesnt_exist -%}
This won't be printed out
{%- else -%}
The doesnt_exist attribute doesn't exist
{%- endif %}
{% if page.doesnt_exist == nil -%}
The doesnt_exist attribute is nil
{%- endif %}
```
You get this output:
```
The does_exist attribute does exist
The doesnt_exist attribute doesn't exist
The doesnt_exist attribute is nil
```
As far as I know there's no way to create your own hashes in pure Liquid.
But you can create them in the
[YAML front matter](https://jekyllrb.com/docs/front-matter/) at the top of the file.
This is a Jekyll feature, not a Liquid feature.
For example:
```yaml
---
my_hash:
some_attr: some_value
another_attr: 23
---
```
Jekyll then makes `my_hash` available as `page.my_hash`. For example
`{{ page.my_hash.some_attr }}` would render
`{{ page.my_hash.some_attr }}`.
A hash in the layout's front matter (available on the `layout` variable)
or in `_config.yml` (available on the `site` variable) would also work.
[Jekyll's data files](https://jekyllrb.com/docs/datafiles/)
would be another way to create your own hashes.
Variable Assignment Tags: `{% assign %}`, `{% capture %}`, `{% increment %}` and `{% decrement %}`
--------------------------------------------------------------------------------------------------
See also:
[Liquid's docs on variables.](https://shopify.github.io/liquid/tags/variable/)
There are four ways to create a new variable in Liquid: the
`{% assign %}`,
`{% capture %}`,
`{% increment %}` and
`{% decrement %}` tags:[^1]
[^1]: With Jekyll you can also create variables for templates to use in YAML front matter, in the config file, or in data files.
With `{% assign %}` you can assign a string, number,
boolean, `nil` or `empty` value to a variable (you can't assign arrays or
hashes):
```liquid
{% assign my_variable = false %}
```
You can then use the newly assigned variable in objects and tags just like any other variable,
for example: `{{ my_variable }}` or `{% if my_variable != true %}`.
`{% capture %}` works for string variables only, it lets
you assign a multiline string and use objects and tags as part of the string.
```liquid
{% capture about_me %}
I am {{ age }}
{% if favorite_food %}
and my favorite food is {{ favorite_food }}
{% endif %}
{% endcapture %}
```
Once captured the variable can be used in objects and tags like any other:
```liquid
{{ about_me }}
```
`{% increment %}` and `{% decrement %}`
are conveniences that create special number variables that automatically
increment by one or decrement by one each time they're printed. The variable
has to be referenced using the `{% increment %}` or
`{% decrement %}` each time you want to print it out.
Unlike `{% assign %}`, `{% capture %}`,
and most Liquid tags, the `{% increment %}` and `{% decrement %}`
tags print out the variable's value in and of themselves, like an object reference would.
For example this:
```liquid
{% increment my_counter %}
{% increment my_counter %}
{% increment my_counter %}
```
prints out this:
```
0
1
2
```
`{% increment %}` counters start from 0.
`{% decrement %}` counters work the same way but they
start from -1 and go down rather than up.
Control Flow Tags: `{% if %}`, `{% unless %}` and `{% case %}`
-----------------------------------------------------------------------------------------------------------------------------
See also:
[Liquid's docs on control flow.](https://shopify.github.io/liquid/tags/control-flow/)
### `{% if %}`, `{% else %}` and `{% elsif %}`
`{% if %}` is the basic control flow tag in Liquid,
along with its helper tags `{% else %}` (which also works
with for loops, see below) and `{% elsif %}`.
Examples:
```liquid
{% if page.categories == empty -%}
This page has no categories.
{%- endif %}
{% if page.draft == true -%}
This page is a draft.
{%- else -%}
This page is not a draft.
{%- endif %}
{% assign num_apples = 4 -%}
{% if num_apples > 5 -%}
There are lots of apples.
{%- elsif num_apples > 3 -%}
There are several apples.
{%- elsif num_apples > 0 -%}
There are a few apples.
{%- else -%}
There are no apples.
{%- endif %}
```
Outputs:
```
This page has no categories.
This page is not a draft.
There are several apples.
```
### Control Flow Operators
See also:
[Liquid's docs on operators.](https://shopify.github.io/liquid/basics/operators/)
Several operators can be used with
`{% if %}` and other control flow tags:
* `==` and `!=` for comparing equality: `{% if post.published == true %}`
(also works with strings, numbers, `nil` and `empty`, even arrays and hashes).
* `<`, `<=`, `>` and `>=` for comparing numbers: `{% if post.tags.size > 0 %}`
* `contains` for checking for a substring in a string, or for a string in an array of strings:
`{% if post.tags contains "Python" %}`.
`contains` only works with strings.
* `and` and `or` can be used to create compound expressions:
#!liquid
{% if post.category == "liquid" and post.draft != true %}
Note: there's no `not` or `!` operator!
### `{% unless %}`
`{% unless %}` is a convenience that's the same as using
`{% if %}` with `!=` instead of `==`.
`{% unless product.title == "Awesome Shoes" %} ... {% endunless %}` is equivalent to
`{% if product.title != "Awesome Shoes" %} ... {% endunless %}`.
`{% else %}` and `{% elsif %}` work with
`{% unless %}` the same as they work with
`{% if %}`.
### `{% case %}` and `{% when %}`
`{% case %}`/`{% when %}` is a convenience for when you'd otherwise
have to write a repetitive `{% if %}`/`{% elsif %}`
block testing multiple different values of the same variable.
A `{% case %}` can have an `{% else %}` on the end just like an `{% if %}`/`{% elsif %}` can.
Here's the example from [the Liquid docs](https://shopify.github.io/liquid/tags/control-flow/):
```liquid
{% case handle %}
{% when "cake" %}
This is a cake
{% when "cookie" %}
This is a cookie
{% else %}
This is not a cake nor a cookie
{% endcase %}
```
Iteration Tags: `{% for %}`, `{% cycle %}` and `{% tablerow %}`
------------------------------------------------------------------------------------------------------------------------------
See also:
[Liquid's docs on iteration.](https://shopify.github.io/liquid/tags/iteration/)
Iteration in Liquid is done using the `{% for %}` tag, its
helpers
`{% else %}`,
`{% break %}`,
`{% continue %}`, and
`{% cycle %}`, and its parameters
`limit`, `offset`, and `reversed`.
There's also a special `(n..m)` syntax for defining **ranges** of numbers to loop through.
Here's some examples:
### For loop over an array
To loop over an array of strings:
```liquid
{%- assign items = "first item, second item, third item" | split: ", " %}
{%- for item in items %}
{{ item }}
{%- endfor %}
```
Outputs:
```html
first item
second item
third item
```
### For loop over a hash
Looping over a hash seems to loop over the keys of the hash.
For example in Jekyll [the `page` variable](https://jekyllrb.com/docs/variables/#global-variables)
is a hash of page-specific information and YAML front matter. Looping over `page` like this:
```liquid
{%- for item in page %}
{{ item }}: {{ page[item] | truncate: 20 }}
{%- endfor %}
```
Outputs this:
```html
content: This is mostly he...
output:
id: /2019/09/29/liquid
url: /2019/09/29/liquid/
next: Breadcrumbs help ...
previous:
<...
path: _posts/2019-09-29...
relative_path: _posts/2019-09-29...
excerpt:
This is mostly...
collection: posts
draft: false
categories: []
subtitle: My Liquid templat...
does_exist: true
foo: bar
my_array: [6, "five", 3.2]
my_hash: {"some_attr"=>"so...
tags: ["Jekyll"]
date: 2019-09-29 00:00:...
title: Liquid Templating...
slug: liquid
ext: .md
layout: post
```
### For loop over a range of numbers
`(n..m)` is a special syntax for creating a range of integers for a for loop.
`n` and `m` can either be literal numbers or variables whose values are numbers.
Examples:
```liquid
{% for i in (3..5) %}
{{ i }}
{% endfor %}
{% assign num = 4 %}
{% for i in (1..num) %}
{{ i }}
{% endfor %}
```
### `{% else %}` tags on for loops
An `{% else %}` tag in a `{% for %}`
loop specifies an alternative block to be rendered if the array is empty:
```liquid
{%- assign items = empty %}
{%- for item in items %}
{{ item }}
{%- else %}
The list is empty.
{%- endfor %}
```
Outputs:
```html
The list is empty.
```
### `{% break %}` and `{% continue %}`
A `{% break %}` tag breaks out of a for loop,
and a `{% continue %}` jumps to the next iteration, as you would expect.
You'd normally use these in an `{% if %}` within a `{% for %}`, of course:
```liquid
{% for item in items %}
{% if should_stop_looping %}
{% break %}
{% elsif should_skip_item %}
{% continue %}
{% else %}
...
{% endif %}
{% endfor %}
```
### `limit`, `offset` and `reversed` parameters
`{% for %}` can accept three parameters limit, offset and reversed.
This:
```liquid
{%- for i in (1..10) reversed limit:3 offset:2 %}
{{ i }}
{%- endfor %}
```
Outputs this:
```
5
4
3
```
When passing multiple parameters to a tag like `{% for %}`
the ordering of the parameters can be tricky: **flags have to come before
parameters with values**.
`reversed` is a flag: a parameter that doesn't take a value, whereas `limit`
and `offset` are parameters that require values. When passing multiple
parameters to a tag, any flag parameters have to be listed before any
parameters with values. Other than that the order of parameters doesn't make
any difference. These are equivalent:
* `{% for i in (1..10) reversed limit:3 offset:2 %}`
* `{% for i in (1..10) reversed offset:2 limit:3 %}`
But these don't work because `reversed` has no effect if it isn't listed before the parameters with values:
* `{% for i in (1..10) limit:3 reversed offset:2 %}`
* `{% for i in (1..10) limit:3 offset:2 reversed %}`
### for loop helper variables
See also:
[The forloop object](https://help.shopify.com/en/themes/liquid/objects/for-loops) in Shopify's Liquid docs.
Several special helper variables are available inside a for loop:
`forloop.length`
: The total number of items being looped over.
`forloop.index`
: The current index of the for loop. Starts at 1.
`forloop.index0`
: The current index of the for loop. Starts at 0.
`forloop.rindex`
: `forloop.index` but in reverse order.
`forloop.rindex0`
: `forloop.index0` but in reverse order.
`forloop.first`
: `true` if this is the first iteration.
`forloop.last`
: `true` if this is the last iteration.
### `{% cycle %}` and `{% tablerow %}`
Finally, the `{% cycle %}` tag is a convenience for
generating alternating or looping values within a loop (for example alternating
or cycling between two or more colours or odd/even classes in a list),
and the `{% tablerow %}` tag is a convenience for generating HTML tables.
See the [Liquid docs](https://shopify.github.io/liquid/tags/iteration/) for how
to use these.
Miscellaneous Filters
---------------------
Filters that don't fit under strings, numbers or arrays:
See also:
The complete list of all filters in the [Liquid docs](https://shopify.github.io/liquid/).