Jekyll Liquid Tags: The Complete Reference Guide
A comprehensive reference for all Jekyll Liquid tags β if, for, assign, capture, include, case, raw, comment, and more with real examples.
Liquid tags are the logic layer of Jekyll templates. Wrapped in {% %} delimiters, they control flow, create variables, loop over data, and embed content β without outputting anything themselves. This is a complete reference for every Liquid tag used in Jekyll.
assign
Creates a variable and assigns it a value. The variable is available for the rest of the template (or until overwritten).
{% assign title = "My Post" %}
{% assign count = site.posts | size %}
{% assign is_premium = false %}
{% assign tags_list = "jekyll,tutorial,liquid" | split: "," %}
Variables created with assign are available in the current scope and any includes called from it.
{% assign author = site.authors | where: "name", page.author | first %}
{% if author %}
<img src="{{ author.avatar }}" alt="{{ author.name }}">
{% endif %}
capture
Builds a string variable from a block of content β useful when the value spans multiple lines or includes Liquid expressions.
{% capture post_url %}{{ site.url }}{{ post.url }}{% endcapture %}
<meta property="og:url" content="{{ post_url }}">
{% capture author_bio %}
{{ page.author }} Β· {{ page.date | date: "%B %-d, %Y" }} Β· {{ page.reading_time }} min read
{% endcapture %}
<p class="post-meta">{{ author_bio | strip }}</p>
Unlike assign, capture captures everything between its opening and closing tags, including whitespace and newlines. Use | strip to remove leading/trailing whitespace.
if / elsif / else / endif
Conditional rendering. Outputs content only when the condition is true.
{% if page.price == 0 %}
<span class="badge">Free</span>
{% elsif page.price < 20 %}
<span class="badge">Budget</span>
{% else %}
<span class="badge">Premium</span>
{% endif %}
Conditions can use:
==equal to!=not equal to>>=<<=numeric comparisonscontainsstring/array containsandboth conditions trueoreither condition true
{% if page.tags contains "jekyll" and page.featured %}
This is a featured Jekyll post.
{% endif %}
{% if user.name == "admin" or user.role == "editor" %}
Show edit button
{% endif %}
Truthy/falsy: In Liquid, nil and false are falsy. Everything else β including 0, "", and [] β is truthy. This differs from many other languages.
{% if page.image %} β false only if image is nil (not set) or false
{% if page.count > 0 %} β more reliable check for zero
unless / endunless
The inverse of if β runs the block when the condition is false. Equivalent to {% if not condition %}.
{% unless page.hide_sidebar %}
{% include sidebar.html %}
{% endunless %}
{% unless forloop.last %}
<hr>
{% endunless %}
case / when / else / endcase
Multi-branch conditional, cleaner than a long if/elsif chain when checking a single variable against multiple values.
{% case page.category %}
{% when "Tutorial" %}
<span class="badge badge--blue">Tutorial</span>
{% when "Comparison" %}
<span class="badge badge--purple">Comparison</span>
{% when "Themes", "Design" %}
<span class="badge badge--green">Design</span>
{% else %}
<span class="badge">Article</span>
{% endcase %}
Multiple values for the same branch: {% when "Themes", "Design" %} matches either.
for / endfor
Loops over an array or range. One of the most-used tags in Jekyll templates.
{% for post in site.posts %}
<article>
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
<p>{{ post.excerpt | strip_html | truncatewords: 25 }}</p>
</article>
{% endfor %}
Loop parameters
{% comment %} First 6 posts {% endcomment %}
{% for post in site.posts limit: 6 %}
{% comment %} Skip the first 3 {% endcomment %}
{% for post in site.posts offset: 3 %}
{% comment %} Reverse order {% endcomment %}
{% for post in site.posts reversed %}
{% comment %} Combine: posts 4-9 {% endcomment %}
{% for post in site.posts limit: 6 offset: 3 %}
forloop variables
Inside a loop, these special variables describe the current iteration:
{% for post in site.posts %}
{% if forloop.first %}<ul>{% endif %}
<li class="{% if forloop.last %}last{% endif %}">
{{ forloop.index }}. {{ post.title }}
</li>
{% if forloop.last %}</ul>{% endif %}
{% endfor %}
| Variable | Value |
|---|---|
forloop.index |
Current iteration, 1-based |
forloop.index0 |
Current iteration, 0-based |
forloop.rindex |
Reverse index, 1-based |
forloop.rindex0 |
Reverse index, 0-based |
forloop.first |
true on first iteration |
forloop.last |
true on last iteration |
forloop.length |
Total number of items |
else in for loops
Runs when the array is empty:
{% for theme in site.themes %}
<div class="theme-card">{{ theme.title }}</div>
{% else %}
<p>No themes available yet.</p>
{% endfor %}
Looping over a number range
{% for i in (1..5) %}
<span>Step {{ i }}</span>
{% endfor %}
Nested loops
{% for category in site.categories %}
<h2>{{ category[0] }}</h2>
{% for post in category[1] %}
<a href="{{ post.url }}">{{ post.title }}</a>
{% endfor %}
{% endfor %}
break and continue
Control loop execution:
{% comment %} Stop after finding first featured post {% endcomment %}
{% for post in site.posts %}
{% if post.featured %}
<a href="{{ post.url }}">{{ post.title }}</a>
{% break %}
{% endif %}
{% endfor %}
{% comment %} Skip drafts in a custom loop {% endcomment %}
{% for post in site.posts %}
{% unless post.published %}{% continue %}{% endunless %}
<li>{{ post.title }}</li>
{% endfor %}
include
Inserts the contents of a file from _includes/. One of the most-used tags in Jekyll layouts.
{% include nav.html %}
{% include footer.html %}
{% include components/card.html %}
include with parameters
Pass variables to an include using key=value pairs:
{% include components/card.html
title=theme.title
url=theme.url
price=theme.price
image=theme.card_image %}
Inside the include, access them with include.keyname:
<!-- _includes/components/card.html -->
<div class="card">
<h3></h3>
<a href="">View</a>
</div>
include with a variable filename
{% assign template = "components/card.html" %}
{% include {{ template }} %}
Or use include_relative to include from a path relative to the current file:
{% include_relative ../shared/notice.html %}
raw / endraw
Prevents Liquid from processing the enclosed content. Essential when writing Liquid code in blog posts.
Use {{ variable }} syntax to output values.
{% if condition %}...{% endif %}
Everything inside ... is output literally, without Liquid processing.
comment / endcomment
Adds a Liquid comment β not rendered in output and not processed:
{% comment %}
TODO: add pagination here
This section is temporarily disabled
{% endcomment %}
Unlike HTML comments (<!-- -->), Liquid comments are completely removed from output and cannot be seen by users in the page source.
highlight / endhighlight
Syntax-highlights a code block using Rouge:
{% highlight ruby %}
def hello
puts "Hello, Jekyll!"
end
{% endhighlight %}
{% highlight javascript linenos %}
const site = "JekyllHub";
console.log(site);
{% endhighlight %}
The optional linenos adds line numbers. The language identifier must be one Rouge supports (ruby, javascript, python, bash, yaml, html, css, json, etc.).
link and post_url
Generate correct URLs for internal pages and posts, accounting for baseurl:
{% comment %} Link to a page (fails build if page not found) {% endcomment %}
<a href="{% link _pages/about.md %}">About</a>
<a href="{% link _posts/2026-08-03-jekyll-directory-structure.md %}">Read post</a>
{% comment %} Link to a post by filename {% endcomment %}
<a href="{% post_url 2026-08-03-jekyll-directory-structure %}">Read post</a>
Both link and post_url cause a build error if the target file does not exist β useful for catching broken internal links. They automatically apply baseurl.
tablerow
Like for, but generates an HTML table:
<table>
{% tablerow plugin in site.data.plugins cols: 3 %}
<td>{{ plugin.name }}</td>
{% endtablerow %}
</table>
cols: sets the number of columns per row. Less commonly used than for, but useful for tabular data.
jekyll_draft and jekyll_version
Available in Jekyll 4+:
{% comment %} Show content only when serving with --drafts flag {% endcomment %}
{% if jekyll.environment == "development" %}
<div class="draft-banner">Draft preview</div>
{% endif %}
Whitespace control
Liquid tags add blank lines to output. Strip whitespace with -:
{%- assign foo = "bar" -%}
{%- for item in list -%}
{{ item }}
{%- endfor -%}
The - inside the tag delimiters strips whitespace (including newlines) before or after that tag. Use this when generating clean HTML or JSON output.
Tags vs filters: the difference
People sometimes confuse tags and filters. The distinction:
- Tags (
{% %}) execute logic β they control flow, assign variables, loop, include files - Filters (
|) transform values β they modify the output of `` expressions
{% assign count = site.posts | size %} β tag with filter in the value
{{ page.title | upcase }} β output with filter
{% for post in site.posts %} β loop tag
Knowing both tags and filters β and which is which β gives you the full toolkit for working with any Jekyll theme.