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 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).
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 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, Jekyll’s docs and 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 haveunless
(which is the opposite ofif
) andelse
, and!=
, but there’s nonot
/!
. -
Truthiness doesn’t work how you expect it to: only
false
andnil
are falsy, everything else is truthy including empty strings, empty arrays, and0
. If your if-statement doesn’t seem to be working this is probably why. -
The 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 instead.
-
Jekyll’s
site.categories
andsite.tags
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:
<p>{{ foo }}</p>
will output this:
<p>bar</p>
Each variable has a type. There are 6 types in Liquid:
- Strings:
"..."
or'...'
(there’s no difference between double and single quotes) - Numbers:
42
or25.672
- Booleans:
true
andfalse
nil
- 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 %}
- 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
:
{% if user -%}
The user is: {{ user }}
{%- else -%}
There is no user.
{%- endif %}
Whitespace Control with {%-
and -%}
The -
characters in {%-
and -%}
are
whitespace control.
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:
There is no user.
When there is a user
variable whose value is "fred"
it outputs:
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 %}
to assign the new object returned by a filter to an existing variable name.)
upcase
is a simple string
manipulation filter that returns an ALL CAPS copy of a given string. This:
{{ "apple" | upcase }}
Outputs:
APPLE
append
is another string
manipulation filter that takes an additional parameter: the suffix string to
append to the first string. This:
The file is: {{ "index" | append: ".html" }}
Outputs:
The file is: index.html
Filters can take multiple positional parameters, but there are no named parameters:
{{ "A long string to be truncated" | truncate: 24, ", and so on" }}
Outputs:
A long string, and so on
Filter parameters can be optional. For example truncate
‘s
second argument defaults to an ellipsis if omitted:
{{ "A long string to be truncated" | truncate: 24 }}
Outputs:
A long string to be t...
Finally, multiple filters can be chained together in a sequence like UNIX pipes:
{{ "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.
<STRING> | capitalize
- a copy of a string with only the first character capitalized
<STRING> | downcase
- an all-lowercase copy of a string
<STRING> | upcase
- a copy of a string in ALL CAPS
<STRING> | lstrip
- a copy of a string with leading whitespace removed
<STRING> | rstrip
- a copy of a string with trailing whitespace removed
<STRING> | strip
- a copy of a string with leading and trailing whitespace removed
<STRING> | strip_html
- a copy of a string with HTML tags removed
<STRING> | strip_newlines
- a copy of a string with line breaks removed
<STRING> | remove: "rain"
- a copy of string with all occurrences of a substring removed
<STRING> | remove_first: "rain"
- a copy of a string with the first occurrence of a substring removed
<STRING> | replace: "my", "your"
- a copy of a string with all occurrences of the first argument replaced with the second argument
<STRING> | replace_first: "my", "your"
- a copy of a string with the first occurrence of the first argument replaced with the second argument
<STRING> | newline_to_br
- a copy of a string with all the
\n
‘s replaced with<br />
‘s <STRING> | escape
- a URL-escaped copy of a string
<STRING> | escape_once
- like
escape
but avoids double-escaping <STRING> | url_encode
- a copy of a string with any URL-unsafe characters percent-encoded
<STRING> | url_decode
- a copy of a string with any percent-encoded characters decoded
<STRING> | append: ".html"
- a new string by concatenating two strings
<STRING> | prepend: "prefix"
- a copy of a string with a prefix string prepended onto the front
<STRING> | size
- the number of characters in a string. Also works with arrays
<STRING> | slice: 0
<STRING> | slice: 2, 5
<STRING> | slice: -3, 2
- a substring of a string
<STRING> | truncate: 25
<STRING> | truncate: 25, ", and so on"
- 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. <STRING> | truncatewords: 3
<STRING> | truncatewords: 3, "--"
- a copy of a string truncated to the given number of words.
<STRING> | 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 arraysThere’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:
{{ <STRING> | split: "" | reverse | join: "" }}
<TIMESTAMP_STRING> | date: "<STRFTIME_FORMAT>"
- a copy of a timestamp string, formatted according to the given strftime format string. Jekyll also adds a lot more date formatting 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.
<NUMBER> | abs
- the absolute value of a number
<NUMBER> | round
<NUMBER> | round: 2
- a copy of a number, rounded to the nearest integer or to a given number of decimal places
<NUMBER> | at_least: 5
- a copy of a number, clamped to a given minimum value
<NUMBER> | at_most: 5
- a copy of a number, clamped to a given maximum value
<NUMBER> | floor
- a copy of a number, rounded down to the nearest whole number
<NUMBER> | ceil
- a copy of a number, rounded up to the nearest whole number
<NUMBER> | plus: 2
- a copy of a number by adding a given number to it
<NUMBER> | minus: 2
- a copy of a number by subtracting a given number from it
<NUMBER> | times: 2
- a copy of a number by multiplying it by a given number
<NUMBER> | divided_by: 4
- a copy of a number by dividing it by a given number
<NUMBER> | modulo: 2
- a new number by taking the remainder from dividing a number by a given number
Arrays
See also: Liquid’s docs on arrays.
Jekyll passes various arrays to your templates as variables, for example:
site.pages
is an array of all your site’s pagessite.posts
is an array of all your site’s posts in reverse chronoligical orderpage.categories
is a list of all this page’s categories, andpage.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:
{% 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:
{% if site.pages == empty %}
This site has no pages.
{% endif %}
is equivalent to this:
{% 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 to split a string into an array of substrings:{% assign my_array = "First String, Second String, Third String" | split: ", " %}
-
You can use ranges to create arrays of numbers for for loops. For example this:
<h2>The 5 Most Recent Posts</h2> <ol> {%- for i in (0..4) %} <li>{{ site.posts[i].title }}</li> {%- endfor %} </ol>
prints a list of the title’s of the site’s 5 most recent posts:
<h2>The 5 Most Recent Posts</h2> <ol> <li>Compiling Python Requirements Files with GNU make</li> <li>How to Publish a Python Package from GitHub Actions</li> <li>How I Use Restic to Back up My Home Folders to Backblaze B2</li> <li>Mutt</li> <li>How to Backup Your Fastmail & Gmail Accounts with isync</li> </ol>
-
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
, andwhere
(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 at the top of your file. This is a Jekyll feature, not a Liquid feature. For example:
--- my_array: [6, five, 3.2] ---
my_array
becomes available as an attribute of thepage
variable. For example this:{% 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 thesite
variable) would also work. -
Jekyll’s data files 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.
<ARRAY> | size
- the number of items in an array. Also works with strings. Arrays also have an
<ARRAY>.size
attribute <ARRAY> | compact
- a copy of an array with any
nil
‘s removed <ARRAY> | concat: other_array
- a new array by concatenating an array witn another array (to join more than two arrays pipe multiple
|concat
‘s together) <ARRAY> | first
- the first item of an array. Arrays also have an
<ARRAY>.first
attribute <ARRAY> | last
- the last item of an array. Arrays also have an
<ARRAY>.last
attribute <ARRAY_OF_STRINGS> | join: " and "
- a string by joining an array of strings into a single string
<ARRAY> | reverse
- a copy of an array with the order of items reversed
<ARRAY> | sort
<ARRAY> | sort: "field"
- 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:
<ARRAY> | sort_natural
<ARRAY> | sort_natural: "field"
- a copy of an array sorted case-insensitively
<ARRAY> | map: "foo"
- an array of the
"foo"
attribute values from each object in the original array <ARRAY> | uniq
- a copy of an array with duplicate items removed
<ARRAY> | where: "available"
<ARRAY> | where: "type", "kitchen"
- 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, for example:
site
is an object containing site-wide information and settings from your_config.yml
filepage
is an object containing page-specific information and metadata from the page’s YAML front matterlayout
is an object containing layout-specific information and metadata from the layout’s YAML front mattersite.pages
is an array of objects, one for each page on your sitesite.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:
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:
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:
{% 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:
{% 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:
{% 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 at the top of the file. This is a Jekyll feature, not a Liquid feature. For example:
---
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 would be another way to create your own hashes.
Variable Assignment Tags: {% assign %}
, {% capture %}
, {% increment %}
and {% decrement %}
See also: Liquid’s docs on variables.
There are four ways to create a new variable in Liquid: the
{% assign %}
,
{% capture %}
,
{% increment %}
and
{% decrement %}
tags:1
With {% assign %}
you can assign a string, number,
boolean, nil
or empty
value to a variable (you can’t assign arrays or hashes):
{% 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.
{% 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:
{{ 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:
{% 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.
{% 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:
{% 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.
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
andempty
, 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
andor
can be used to create compound expressions:{% 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:
{% 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.
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:
{%- assign items = "first item, second item, third item" | split: ", " %}
<ol>
{%- for item in items %}
<li>{{ item }}</li>
{%- endfor %}
</ol>
Outputs:
<ol>
<li>first item</li>
<li>second item</li>
<li>third item</li>
</ol>
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
is a hash of page-specific information and YAML front matter. Looping over page
like this:
<ol>
{%- for item in page %}
<li>{{ item }}: {{ page[item] | truncate: 20 }}</li>
{%- endfor %}
</ol>
Outputs this:
<ol>
<li>content: This is mostly he...</li>
<li>output: </li>
<li>id: /2019/09/29/liquid</li>
<li>url: /2019/09/29/liquid/</li>
<li>next: Breadcrumbs help ...</li>
<li>previous: <!DOCTYPE html>
<...</li>
<li>path: _posts/2019-09-29...</li>
<li>relative_path: _posts/2019-09-29...</li>
<li>excerpt: <p>This is mostly...</li>
<li>collection: posts</li>
<li>draft: false</li>
<li>categories: []</li>
<li>subtitle: My Liquid templat...</li>
<li>does_exist: true</li>
<li>foo: bar</li>
<li>my_array: [6, "five", 3.2]</li>
<li>my_hash: {"some_attr"=>"so...</li>
<li>tags: ["Jekyll"]</li>
<li>date: 2019-09-29 00:00:...</li>
<li>title: Liquid Templating...</li>
<li>slug: liquid</li>
<li>ext: .md</li>
<li>layout: post</li>
</ol>
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:
{% 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:
{%- assign items = empty %}
<ol>
{%- for item in items %}
<li>{{ item }}</li>
{%- else %}
The list is empty.
{%- endfor %}
</ol>
Outputs:
<ol>
The list is empty.
</ol>
{% 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:
{% 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:
{%- 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 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 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.
<OBJECT> | default: <FALLBACK_VALUE>
- Either returns
<OBJECT>
or, if<OBJECT>
isfalse
,nil
orempty
, returns<FALLBACK_VALUE>
instead
Comments
Comments can be entered using the {% comment %}
tag:
See also: Liquid’s docs on comments.
{% comment %}
This is a comment. It will be omitted from the rendered output.
{% endcomment %}
Kramdown comments also work on GitHub Pages.
Jekyll Liquid
Jekyll adds custom filters, custom tags, global variables, a very important
{% include %}
tag, and template inheritance (“layouts”)
on top of standard Liquid:
- Jekyll’s Liquid filters
- Jekyll’s Liquid tags
- Jekyll’s Liquid variables
- Jekyll’s
{% include %}
tag - Jekyll’s layout inheritance
Jekyll’s {% link %}
and {% post_url %}
See also: Jekyll’s docs on Links.
Jekyll’s {% link %}
tag, for generating URLs to pages
and posts of your site, is particularly useful.
{% link news/index.html %}
returns the URL to the news/index.html
page.
{% link _posts/2016-07-26-name-of-post.md %}
returns the URL to the 2016-07-26-name-of-post.md
post.
The {% post_url %}
tag is a similar but more convenient
tag for generating URLs to posts only: {% post_url 2010-07-21-name-of-post %}
or {% post_url /subdir/2010-07-21-name-of-post %}
.
The jekyll-relative-links
plugin is enabled by default on GitHub Pages
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
:
relative_links:
enabled: true
collections: true
Jekyll’s includes
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) code in an _includes/foo.html
file and then include that code in another template, page or post whenever you
want it with {% include foo.html %}
.
There’s also {% include_relative %}
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:
{% include my_include.html foo="foo" bar="bar" %}
These are then available in my_include.html
as {{ include.foo }}
and {{ include.bar }}
.
Optional include parameters can be implemented by using if-statements like
{% if include.foo %} ... {% else %} ... {% endif %}
,
or by using the |default
filter.
Captures are often useful when passing parameters into includes.
Jekyll’s site.categories
and site.tags
See also: Jekyll’s docs on 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):
[
[
"<name of first category>",
[
{
"title": "<title of first post in category>",
"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:
<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>
As well as looping over them, you can also access individual tags or categories by name:
The "Jekyll" tag has {{ site.tags.Jekyll.size }} posts.
{% for post in site.tags.Jekyll limit:3 %}
{{ post.title }}
{% endfor %}
Arbitrary front matter are available as variables
See also: Front Matter in Jekyll’s docs.
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:
---
foo: bar
---
You could do this to render “bar”:
{{ page.foo }}
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
See also: Configuration in Jekyll’s docs.
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
:
foo: bar
You could do this to render “bar”:
{{ site.foo }}
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 in the Jekyll docs.
-
With Jekyll you can also create variables for templates to use in YAML front matter, in the config file, or in data files. ↩