--- subtitle: My Liquid templating crash course, including Jekyll- and GitHub Pages-specific details. does_exist: true foo: bar my_array: [6, five, 3.2] my_hash: some_attr: some_value another_attr: 23 tags: [Jekyll] --- Liquid Templating Crash Course ============================== 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/{{ site.github.repository_nwo }}/master/{{ page.path }})). 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. * This unordered list will be replaced with a table of contents. {:toc} 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: `{% raw %}{% assign my_string = "foo" %}{% endraw %}`. 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: {% raw %}`{{ ... }}`{% endraw %} 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

{% raw %}{{ foo }}{% endraw %}

``` will output this: ```html

{{ foo }}

``` 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 `{% raw %}{% if my_array == empty %}{% endraw %}` is equivalent to `{% raw %}{% if my_array.size == 0 %}{% endraw %}` 6. [Hashes](#hashes): objects with multiple fields ### 2. Tags: `{% raw %}{% ... %}{% endraw %}` Tags implement **control flow**, **iteration** and **variable assignments**, among other things. For example, `{% raw %}{% if %}{% endraw %}` 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 {%- raw -%} {% if user -%} The user is: {{ user }} {%- else -%} There is no user. {%- endif %} {% endraw %} ```
Whitespace Control with {% raw %}{%-{% endraw %} and {% raw %}-%}{% endraw %} The `-` characters in {% raw %}`{%-`{% endraw %} and {% raw %}`-%}`{% endraw %} are [whitespace control](https://shopify.github.io/liquid/basics/whitespace/). A {% raw %}`{%-`{% endraw %} strips any leading whitespace from before the tag's contents, in the rendered output. It strips spaces, tabs and newlines. {% raw %}`-%}`{% endraw %} 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 {% raw %}`{% if %}`{% endraw %} tag above outputs: ```html {% if user -%} The user is: {{ user }} {%- else -%} There is no user. {%- endif %} ``` When there _is_ a `user` variable whose value is `"fred"` it outputs: {%- assign user = "fred" %} ```html {% if user -%} The user is: {{ user }} {%- else -%} There is no user. {%- endif %} ``` ### 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 [`{% raw %}{% assign %}{% endraw %}`](#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 {%- raw -%} {{ "apple" | upcase }} {% endraw %} ``` Outputs: ``` {{ "apple" | upcase }} ``` [`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 {%- raw -%} The file is: {{ "index" | append: ".html" }} {% endraw %} ``` Outputs: ```html The file is: {{ "index" | append: ".html" }} ``` Filters can take multiple positional parameters, but there are no named parameters: ```liquid {%- raw -%} {{ "A long string to be truncated" | truncate: 24, ", and so on" }} {% endraw %} ``` Outputs: ```html {{ "A long string to be truncated" | truncate: 24, ", 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 {%- raw -%} {{ "A long string to be truncated" | truncate: 24 }} {% endraw %} ``` Outputs: ```html {{ "A long string to be truncated" | truncate: 24 }} ``` Finally, multiple filters can be chained together in a sequence like UNIX pipes: ```liquid {%- raw -%} {{ "adam!" | capitalize | prepend: "Hello " }} {% endraw %} ``` 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: `{% raw %}{{ | split: "" | reverse | join: "" }}{% endraw %}` [` | 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: * `{% raw %}{{ site.posts[0]{% endraw %} }}` is the newest post * `{% raw %}{{ site.posts[-1]{% endraw %} }}` 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: * `{% raw %}{{ site.posts.first.title{% endraw %} }}` is the newest post * `{% raw %}{{ site.posts.last.title{% endraw %} }}` is the oldest post You can loop over an array with a [for loop](#iteration-tags-for-cycle-and-tablerow): ```liquid {%- raw -%} {% for post in site.posts %} {{ post.title }} {% endfor %} {% endraw %} ``` 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 {%- raw -%} {% if site.pages == empty %} This site has no pages. {% endif %} {% endraw %} ``` is equivalent to this: ```liquid {%- raw -%} {% if site.pages.size == 0 %} This site has no pages. {% endif %} {% endraw %} ``` 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: `{% raw %}{% assign my_array = "First String, Second String, Third String" | split: ", " %}{% endraw %}` * You can use [ranges](#looping-over-a-range-of-numbers) to create arrays of numbers for for loops. For example this: ```liquid {%- raw -%}

The 5 Most Recent Posts

    {%- for i in (0..4) %}
  1. {{ site.posts[i].title }}
  2. {%- endfor %}
{% endraw %} ``` prints a list of the title's of the site's 5 most recent posts: ```html

The 5 Most Recent Posts

    {%- for i in (0..4) %}
  1. {{ site.posts[i].title }}
  2. {%- endfor %}
``` * 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: `{% raw %}{% assign draft_posts = site.posts | where: "draft", true %}{% endraw %}` * 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: ```yaml --- my_array: [6, five, 3.2] --- ``` `my_array` becomes available as an attribute of the `page` variable. For example this: ```liquid {%- raw -%} {% for item in page.my_array %} {{ item }} {%- endfor %} {% endraw %} ``` Outputs: ```html {% for item in page.my_array %} {{ item }} {%- endfor %} ``` 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 {%- raw -%} Site title: {{ site.title }} Site description: {{ site.description }} Site base URL: {{ site.baseurl }} Site last updated at: {{ site.time }} {% endraw %} ``` This outputs: ```html Site title: {{ site.title }} Site description: {{ site.description }} Site base URL: {{ site.baseurl }} Site last updated at: {{ site.time }} ``` 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 {%- raw -%} Page title: {{ page["title"] }} Beginning of page content: {{ page["content"] | truncate }} Page URL: {{ page["url"] }} {% endraw %} ``` This outputs: ```html Page title: {{ page["title"] }} Beginning of page content: {{ page["content"] | truncate }} Page URL: {{ page["url"] }} ``` You can also put a string variable or an expression that resolves to a string in `[]`'s, for example: ```liquid {%- raw -%} {% assign tag = "Jekyll" %} Number of posts tagged "{{ tag }}": {{ site.tags[tag].size }} {% endraw %} ``` You can loop over a hash with `{% raw %}{% for %}{% endraw %}` and it loops over the hash's keys: ```liquid {%- raw -%} {% for key in site.github limit:3 -%} {{ key }}: {{ site.github[key] }} {% endfor %} {% endraw %} ``` Output: ```html {% for key in site.github limit:3 -%} {{ key }}: {{ site.github[key] }} {% endfor %} ``` 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 {%- raw -%} {% 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 %} {% endraw %} ``` You get this output: ```html {% 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 %} ``` 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 `{% raw %}{{ page.my_hash.some_attr }}{% endraw %}` 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: `{% raw %}{% assign %}{% endraw %}`, `{% raw %}{% capture %}{% endraw %}`, `{% raw %}{% increment %}{% endraw %}` and `{% raw %}{% decrement %}{% endraw %}` --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
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 `{% raw %}{% assign %}{% endraw %}`, `{% raw %}{% capture %}{% endraw %}`, `{% raw %}{% increment %}{% endraw %}` and `{% raw %}{% decrement %}{% endraw %}` 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 `{% raw %}{% assign %}{% endraw %}` you can assign a string, number, boolean, `nil` or `empty` value to a variable (you can't assign arrays or hashes): ```liquid {%- raw -%} {% assign my_variable = false %} {% endraw %} ``` You can then use the newly assigned variable in objects and tags just like any other variable, for example: `{% raw %}{{ my_variable }}{% endraw %}` or `{% raw %}{% if my_variable != true %}{% endraw %}`. `{% raw %}{% capture %}{% endraw %}` works for string variables only, it lets you assign a multiline string and use objects and tags as part of the string. ```liquid {%- raw -%} {% capture about_me %} I am {{ age }} {% if favorite_food %} and my favorite food is {{ favorite_food }} {% endif %} {% endcapture %} {% endraw %} ``` Once captured the variable can be used in objects and tags like any other: ```liquid {%- raw -%} {{ about_me }} {% endraw %} ``` `{% raw %}{% increment %}{% endraw %}` and `{% raw %}{% decrement %}{% endraw %}` 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 `{% raw %}{% increment %}{% endraw %}` or `{% raw %}{% decrement %}{% endraw %}` each time you want to print it out. Unlike `{% raw %}{% assign %}{% endraw %}`, `{% raw %}{% capture %}{% endraw %}`, and most Liquid tags, the `{% raw %}{% increment %}{% endraw %}` and `{% raw %}{% decrement %}{% endraw %}` tags print out the variable's value in and of themselves, like an object reference would. For example this: ```liquid {%- raw -%} {% increment my_counter %} {% increment my_counter %} {% increment my_counter %} {% endraw %} ``` prints out this: ```html {% increment my_counter %} {% increment my_counter %} {% increment my_counter %} ``` `{% raw %}{% increment %}{% endraw %}` counters start from 0. `{% raw %}{% decrement %}{% endraw %}` counters work the same way but they start from -1 and go down rather than up. Control Flow Tags: `{% raw %}{% if %}{% endraw %}`, `{% raw %}{% unless %}{% endraw %}` and `{% raw %}{% case %}{% endraw %}` -----------------------------------------------------------------------------------------------------------------------------
See also: [Liquid's docs on control flow.](https://shopify.github.io/liquid/tags/control-flow/)
### `{% raw %}{% if %}{% endraw %}`, `{% raw %}{% else %}{% endraw %}` and `{% raw %}{% elsif %}{% endraw %}` `{% raw %}{% if %}{% endraw %}` is the basic control flow tag in Liquid, along with its helper tags `{% raw %}{% else %}{% endraw %}` (which also works with for loops, see below) and `{% raw %}{% elsif %}{% endraw %}`. Examples: ```liquid {%- raw -%} {% 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 %} {% endraw %} ``` Outputs: ```html {% 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 %} ``` ### Control Flow Operators
See also: [Liquid's docs on operators.](https://shopify.github.io/liquid/basics/operators/)
Several operators can be used with `{% raw %}{% if %}{% endraw %}` and other control flow tags: * `==` and `!=` for comparing equality: `{% raw %}{% if post.published == true %}{% endraw %}` (also works with strings, numbers, `nil` and `empty`, even arrays and hashes). * `<`, `<=`, `>` and `>=` for comparing numbers: `{% raw %}{% if post.tags.size > 0 %}{% endraw %}` * `contains` for checking for a substring in a string, or for a string in an array of strings: `{% raw %}{% if post.tags contains "Python" %}{% endraw %}`. `contains` only works with strings. * `and` and `or` can be used to create compound expressions: ```liquid {%- raw -%} {% if post.category == "liquid" and post.draft != true %} {% endraw %} ```
Note: there's no `not` or `!` operator!
### `{% raw %}{% unless %}{% endraw %}` `{% raw %}{% unless %}{% endraw %}` is a convenience that's the same as using `{% raw %}{% if %}{% endraw %}` with `!=` instead of `==`. `{% raw %}{% unless product.title == "Awesome Shoes" %} ... {% endunless %}{% endraw %}` is equivalent to `{% raw %}{% if product.title != "Awesome Shoes" %} ... {% endunless %}{% endraw %}`. `{% raw %}{% else %}{% endraw %}` and `{% raw %}{% elsif %}{% endraw %}` work with `{% raw %}{% unless %}{% endraw %}` the same as they work with `{% raw %}{% if %}{% endraw %}`. ### `{% raw %}{% case %}{% endraw %}` and `{% raw %}{% when %}{% endraw %}` `{% raw %}{% case %}{% endraw %}`/`{% raw %}{% when %}{% endraw %}` is a convenience for when you'd otherwise have to write a repetitive `{% raw %}{% if %}{% endraw %}`/`{% raw %}{% elsif %}{% endraw %}` block testing multiple different values of the same variable. A `{% raw %}{% case %}{% endraw %}` can have an `{% raw %}{% else %}{% endraw %}` on the end just like an `{% raw %}{% if %}{% endraw %}`/`{% raw %}{% elsif %}{% endraw %}` can. Here's the example from [the Liquid docs](https://shopify.github.io/liquid/tags/control-flow/): ```liquid {%- raw -%} {% case handle %} {% when "cake" %} This is a cake {% when "cookie" %} This is a cookie {% else %} This is not a cake nor a cookie {% endcase %} {% endraw %} ``` Iteration Tags: `{% raw %}{% for %}{% endraw %}`, `{% raw %}{% cycle %}{% endraw %}` and `{% raw %}{% tablerow %}{% endraw %}` ------------------------------------------------------------------------------------------------------------------------------
See also: [Liquid's docs on iteration.](https://shopify.github.io/liquid/tags/iteration/)
Iteration in Liquid is done using the `{% raw %}{% for %}{% endraw %}` tag, its helpers `{% raw %}{% else %}{% endraw %}`, `{% raw %}{% break %}{% endraw %}`, `{% raw %}{% continue %}{% endraw %}`, and `{% raw %}{% cycle %}{% endraw %}`, 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 {%- raw -%} {%- assign items = "first item, second item, third item" | split: ", " %}
    {%- for item in items %}
  1. {{ item }}
  2. {%- endfor %}
{% endraw %} ``` Outputs: ```html {%- assign items = "first item, second item, third item" | split: ", " %}
    {%- for item in items %}
  1. {{ item }}
  2. {%- endfor %}
``` ### 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 {%- raw -%}
    {%- for item in page %}
  1. {{ item }}: {{ page[item] | truncate: 20 }}
  2. {%- endfor %}
{% endraw %} ``` Outputs this: ```
    {%- for item in page %}
  1. {{ item }}: {{ page[item] | truncate: 20 }}
  2. {%- endfor %}
``` ### 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 {%- raw -%} {% for i in (3..5) %} {{ i }} {% endfor %} {% assign num = 4 %} {% for i in (1..num) %} {{ i }} {% endfor %} {% endraw %} ``` ### `{% raw %}{% else %}{% endraw %}` tags on for loops An `{% raw %}{% else %}{% endraw %}` tag in a `{% raw %}{% for %}{% endraw %}` loop specifies an alternative block to be rendered if the array is empty: ```liquid {%- raw -%} {%- assign items = empty %}
    {%- for item in items %}
  1. {{ item }}
  2. {%- else %} The list is empty. {%- endfor %}
{% endraw %} ``` Outputs: ```html {%- assign items = empty %}
    {%- for item in items %}
  1. {{ item }}
  2. {%- else %} The list is empty. {%- endfor %}
``` ### `{% raw %}{% break %}{% endraw %}` and `{% raw %}{% continue %}{% endraw %}` A `{% raw %}{% break %}{% endraw %}` tag breaks out of a for loop, and a `{% raw %}{% continue %}{% endraw %}` jumps to the next iteration, as you would expect. You'd normally use these in an `{% raw %}{% if %}{% endraw %}` within a `{% raw %}{% for %}{% endraw %}`, of course: ```liquid {%- raw -%} {% for item in items %} {% if should_stop_looping %} {% break %} {% elsif should_skip_item %} {% continue %} {% else %} ... {% endif %} {% endfor %} {% endraw %} ``` ### `limit`, `offset` and `reversed` parameters `{% raw %}{% for %}{% endraw %}` can accept three parameters limit, offset and reversed. This: ```liquid {%- raw -%} {%- for i in (1..10) reversed limit:3 offset:2 %} {{ i }} {%- endfor %} {% endraw %} ``` Outputs this: ```html {%- for i in (1..10) reversed limit:3 offset:2 %} {{ i }} {%- endfor %} ``` When passing multiple parameters to a tag like `{% raw %}{% for %}{% endraw %}` 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: * `{% raw %}{% for i in (1..10) reversed limit:3 offset:2 %}{% endraw %}` * `{% raw %}{% for i in (1..10) reversed offset:2 limit:3 %}{% endraw %}` But these don't work because `reversed` has no effect if it isn't listed before the parameters with values: * `{% raw %}{% for i in (1..10) limit:3 reversed offset:2 %}{% endraw %}` * `{% raw %}{% for i in (1..10) limit:3 offset:2 reversed %}{% endraw %}` ### 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. ### `{% raw %}{% cycle %}{% endraw %}` and `{% raw %}{% tablerow %}{% endraw %}` Finally, the `{% raw %}{% cycle %}{% endraw %}` 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 `{% raw %}{% tablerow %}{% endraw %}` 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/).
[` | default: `](https://shopify.github.io/liquid/filters/default/) : Either returns `` or, if `` is `false`, `nil` or `empty`, returns `` instead Comments -------- Comments can be entered using the `{% raw %}{% comment %}{% endraw %}` tag:
See also: [Liquid's docs on comments.](https://shopify.github.io/liquid/tags/comment/)
```liquid {%- raw -%} {% comment %} This is a comment. It will be omitted from the rendered output. {% endcomment %} {% endraw %} ``` [Kramdown comments](https://kramdown.gettalong.org/syntax.html#extensions) also work on GitHub Pages. {::comment} This is a Kramdown comment. It should not be printed. {:/comment} Jekyll Liquid ------------- Jekyll adds custom filters, custom tags, global variables, a very important `{% raw %}{% include %}{% endraw %}` tag, and template inheritance ("layouts") on top of standard Liquid: * [Jekyll's Liquid filters](https://jekyllrb.com/docs/liquid/filters/) * [Jekyll's Liquid tags](https://jekyllrb.com/docs/liquid/tags/) * [Jekyll's Liquid variables](https://jekyllrb.com/docs/variables/) * [Jekyll's `{% raw %}{% include %}{% endraw %}` tag](https://jekyllrb.com/docs/includes/) * [Jekyll's layout inheritance](https://jekyllrb.com/docs/layouts/#inheritance) ### Jekyll's `{% raw %}{% link %}{% endraw %}` and `{% raw %}{% post_url %}{% endraw %}`
See also: Jekyll's docs on [Links](https://jekyllrb.com/docs/liquid/tags/#links).
Jekyll's `{% raw %}{% link %}{% endraw %}` tag, for generating URLs to pages and posts of your site, is particularly useful. `{% raw %}{% link news/index.html %}{% endraw %}` returns the URL to the `news/index.html` page. `{% raw %}{% link _posts/2016-07-26-name-of-post.md %}{% endraw %}` returns the URL to the `2016-07-26-name-of-post.md` post. The `{% raw %}{% post_url %}{% endraw %}` tag is a similar but more convenient tag for generating URLs to posts only: `{% raw %}{% post_url 2010-07-21-name-of-post %}{% endraw %}` or `{% raw %}{% post_url /subdir/2010-07-21-name-of-post %}{% endraw %}`. The [jekyll-relative-links](https://github.com/benbalter/jekyll-relative-links) plugin is [enabled by default on GitHub Pages](https://help.github.com/en/articles/about-github-pages-and-jekyll#plugins) and provides an even easier way to link to posts and pages: any normal Markdown link that points to a Markdown file (relative to the location of the current file) gets turned into a link to that Markdown file's page or post's permalink URL. These kind of links also work when the Markdown is rendered and previewed on GitHub.com: `[foo](bar.md)` gets converted to `[foo](/bar/)` (`bar.md`'s permalink according to your `permalink` style setting). To enable these kind of relative links everywhere put this in your `_config.yml`: ```yaml relative_links: enabled: true collections: true ``` ### Jekyll's includes
See also: Jekyll's docs on [Includes](https://jekyllrb.com/docs/includes/) and Liquid's docs on [capture](https://shopify.github.io/liquid/tags/variable/#capture).
Includes are a way of compartmentalizing your templates into lots of small, reusable templates. It's a good idea to use as many of these as possible. The basic idea is that you create a snippet of Liquid, HTML and Markdown ([Kramdown](https://kramdown.gettalong.org)) code in an `_includes/foo.html` file and then include that code in another template, page or post whenever you want it with `{% raw %}{% include foo.html %}{% endraw %}`. There's also `{% raw %}{% include_relative %}{% endraw %}` which searches for an include file relative to the location of the current file, instead of looking in the global `_includes/` directory. You can pass one or more parameters into an include: ```liquid {%- raw -%} {% include my_include.html foo="foo" bar="bar" %} {% endraw %} ``` These are then available in `my_include.html` as `{% raw %}{{ include.foo }}{% endraw %}` and `{% raw %}{{ include.bar }}{% endraw %}`. Optional include parameters can be implemented by using if-statements like `{% raw %}{% if include.foo %} ... {% else %} ... {% endif %}{% endraw %}`, or by using [the `|default` filter](https://shopify.github.io/liquid/filters/default/). [Captures](https://shopify.github.io/liquid/tags/variable/#capture) are often useful when passing parameters into includes. ### Jekyll's `site.categories` and `site.tags`
See also: Jekyll's docs on [Categories and Tags](https://jekyllrb.com/docs/posts/#categories-and-tags) (includes a for-loop similar to the one below for listing all posts by category).
Jekyll's `site.categories` and `site.tags` are arrays of arrays, one subarray for each category or tag, where each category or tag subarray contains two elements: `[0]`: the name of the category or tag (a string), and `[1]`: a subsubarray containing all the posts (post objects with various fields) with that category or tag: The structure of `site.categories` looks something like this (and `site.tags` is the same): ```json [ [ "", [ { "title": "", "url": "<URL of first post in category>", ... }, { "title": "<title of second post in category>", "url": "<URL of third post in category>", ... }, ... ], ], [ "<name of second category>", [ {<first post in category>}, {<second post in category>}, ], ], ... ] ``` For example here's some code to loop over all of a site's tags and, for each tag, print out a list of links to all of the posts with that tag: ```liquid {%- raw -%} <ul> {% for tag in site.tags %} <li> <p>{{ tag[0] }}:</p> <!-- The name of the tag. --> <ol> {% for post in tag[1] %} <!-- Loop over all of the tag's posts. --> <li> <a href="{{ post.url | absolute_url }}"> {{ post.title }} </a> </li> {% endfor %} </ol> </li> {% endfor %} </ul> {% endraw %} ``` As well as looping over them, you can also access individual tags or categories by name: ```liquid {%- raw -%} The "Jekyll" tag has {{ site.tags.Jekyll.size }} posts. {% for post in site.tags.Jekyll limit:3 %} {{ post.title }} {% endfor %} {% endraw %} ``` ### Arbitrary front matter are available as variables <div class="seealso" markdown="1"> See also: [Front Matter](https://jekyllrb.com/docs/front-matter/) in Jekyll's docs. </div> You can put arbitrary key/value pairs in the YAML front matter at the top of a page or post file and Jekyll makes them available as attributes of the `page` object. For example given this YAML front matter at the top of a page or post file: ```yaml --- foo: bar --- ``` You could do this to render "bar": ```liquid {%- raw -%} {{ page.foo }} {% endraw %} ``` You can also put key/value pairs in the front matter of layout files, and Jekyll makes them available as attributes of the global variable `layout`. ### Arbitrary config settings are available as variables <div class="seealso" markdown="1"> See also: [Configuration](https://jekyllrb.com/docs/configuration/) in Jekyll's docs. </div> You can put arbitrary key/value pairs in your site's `_config.yml` file and Jekyll makes them available as attributes of the global variable `site`. For example given this line on `_config.yml`: ```yaml foo: bar ``` You could do this to render "bar": ```liquid {%- raw -%} {{ site.foo }} {% endraw %} ``` ### YAML, JSON, CSV and TSV files in `_data` are accessible as variables You can put YAML, JSON, CSV and TSV files in a `_data` folder and Jekyll makes them all available as attributes of a `site.data` object. See [Data Files](https://jekyllrb.com/docs/datafiles/) in the Jekyll docs.