Home β€Ί Blog β€Ί Jekyll Liquid Tags: The Complete Reference Guide
Tutorial

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.

Jekyll Liquid Tags: The Complete Reference Guide

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 comparisons
  • contains string/array contains
  • and both conditions true
  • or either 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.

Share LinkedIn