Jekyll Liquid Filters: The Complete Cheatsheet (2026)
Every Liquid filter you need for Jekyll β string manipulation, date formatting, array filters, URL helpers, and Jekyll-specific additions. With examples.
Liquid filters transform output in Jekyll templates. They are chained with the pipe character | and applied to variables, strings, numbers, arrays, and dates. This cheatsheet covers every filter you will actually use, with real examples.
String filters
append and prepend
{{ "Jekyll" | append: " Themes" }}
<!-- output: Jekyll Themes -->
{{ "Themes" | prepend: "Jekyll " }}
<!-- output: Jekyll Themes -->
upcase, downcase, capitalize
{{ "hello world" | upcase }}
<!-- output: HELLO WORLD -->
{{ "HELLO WORLD" | downcase }}
<!-- output: hello world -->
{{ "hello world" | capitalize }}
<!-- output: Hello world -->
strip, lstrip, rstrip
Removes whitespace from both ends, left only, or right only.
{{ " hello " | strip }}
<!-- output: hello -->
replace and replace_first
{{ "Jekyll is great, Jekyll is fast" | replace: "Jekyll", "JekyllHub" }}
<!-- output: JekyllHub is great, JekyllHub is fast -->
{{ "Jekyll is great, Jekyll is fast" | replace_first: "Jekyll", "JekyllHub" }}
<!-- output: JekyllHub is great, Jekyll is fast -->
remove and remove_first
{{ "Hello, World!" | remove: "," }}
<!-- output: Hello World! -->
truncate and truncatewords
{{ "The quick brown fox" | truncate: 10 }}
<!-- output: The qui... -->
{{ "The quick brown fox" | truncatewords: 3 }}
<!-- output: The quick brown... -->
split and join
{% assign tags = "jekyll,blog,themes" | split: "," %}
{{ tags | join: " Β· " }}
<!-- output: jekyll Β· blog Β· themes -->
slice
Returns a substring starting at the given index.
{{ "Jekyll" | slice: 0, 3 }}
<!-- output: Jek -->
strip_html
Removes all HTML tags from a string β useful for generating meta descriptions from post content.
{{ post.content | strip_html | truncate: 160 }}
strip_newlines
{{ content | strip_newlines }}
newline_to_br
Converts newlines to <br> tags.
{{ content | newline_to_br }}
escape and escape_once
HTML-encodes a string.
{{ '<script>' | escape }}
<!-- output: <script> -->
url_encode and url_decode
{{ "hello world" | url_encode }}
<!-- output: hello+world -->
markdownify
Converts a Markdown string to HTML. Useful for front matter fields written in Markdown.
{{ page.description | markdownify }}
jsonify
Converts an object to JSON. Essential for passing Jekyll data to JavaScript.
<script>
var themes = {{ site.themes | jsonify }};
</script>
Number filters
plus, minus, times, divided_by, modulo
{{ 10 | plus: 5 }} <!-- 15 -->
{{ 10 | minus: 3 }} <!-- 7 -->
{{ 4 | times: 3 }} <!-- 12 -->
{{ 10 | divided_by: 3 }} <!-- 3 (integer division) -->
{{ 10 | modulo: 3 }} <!-- 1 -->
ceil, floor, round, abs
{{ 4.3 | ceil }} <!-- 5 -->
{{ 4.7 | floor }} <!-- 4 -->
{{ 4.567 | round: 2 }} <!-- 4.57 -->
{{ -5 | abs }} <!-- 5 -->
Array filters
size
Works on strings and arrays.
{{ site.themes | size }}
{{ "Jekyll" | size }} <!-- 6 -->
first and last
{{ site.themes | first }}
{{ site.themes | last }}
push and pop, shift and unshift
Add or remove items from an array.
{% assign updated = site.themes | push: new_theme %}
concat
Merges two arrays.
{% assign all_posts = site.posts | concat: site.pages %}
map
Extracts a single property from an array of objects.
{% assign titles = site.themes | map: "title" %}
{{ titles | join: ", " }}
where and where_exp
Filter an array by a property value.
{% assign premium = site.themes | where: "price_type", "premium" %}
{% assign recent = site.posts | where_exp: "post", "post.date > '2026-01-01'" %}
sort and sort_natural
{% assign sorted = site.themes | sort: "title" %}
{% assign sorted = site.themes | sort_natural: "title" %}
reverse
{% assign reversed = site.posts | reverse %}
uniq
Removes duplicate values from an array.
{% assign unique_cats = site.themes | map: "category" | uniq %}
group_by and group_by_exp
Groups an array of objects by a property.
{% assign by_category = site.themes | group_by: "category" %}
{% for group in by_category %}
<h2>{{ group.name }}</h2>
{% for theme in group.items %}
<p>{{ theme.title }}</p>
{% endfor %}
{% endfor %}
find and find_exp
Returns the first item matching a condition (Jekyll 4+).
{% assign featured = site.themes | find: "featured", true %}
sum
Adds up a numeric property across an array.
{{ site.themes | map: "stars" | sum }}
compact
Removes nil values from an array.
{% assign clean = array | compact %}
flatten
Flattens a nested array.
{% assign flat = nested_array | flatten %}
Date filters
date
Formats a date using strftime syntax.
{{ page.date | date: "%B %-d, %Y" }}
<!-- output: July 3, 2026 -->
{{ page.date | date: "%Y-%m-%d" }}
<!-- output: 2026-07-03 -->
{{ page.date | date: "%d %b %Y" }}
<!-- output: 03 Jul 2026 -->
Common format codes:
%Yβ four-digit year%mβ zero-padded month (01β12)%-mβ month without padding (1β12)%Bβ full month name%bβ abbreviated month name%dβ zero-padded day%-dβ day without padding%Aβ full weekday name%H:%Mβ 24-hour time
date_to_long_string and date_to_string
Jekyll shortcuts for common date formats.
{{ page.date | date_to_long_string }}
<!-- output: 03 July 2026 -->
{{ page.date | date_to_string }}
<!-- output: 03 Jul 2026 -->
date_to_xmlschema and date_to_rfc822
Used in feeds and structured data.
{{ page.date | date_to_xmlschema }}
<!-- output: 2026-07-03T00:00:00+00:00 -->
URL and path filters
relative_url and absolute_url
Essential β always use these instead of hardcoded paths.
<a href="{{ '/themes/' | relative_url }}">Browse themes</a>
<meta property="og:url" content="{{ page.url | absolute_url }}">
slugify
Converts a string to a URL-safe slug.
{{ "Hello World! How are you?" | slugify }}
<!-- output: hello-world-how-are-you -->
uri_escape
Percent-encodes a URI.
{{ "search query" | uri_escape }}
<!-- output: search%20query -->
cgi_escape
CGI-escapes a string (spaces become +).
{{ "hello world" | cgi_escape }}
<!-- output: hello+world -->
Miscellaneous
default
Returns a default value when the variable is nil, false, or empty.
{{ page.author | default: site.author.name }}
{{ page.image | default: site.og_image | relative_url }}
inspect
Outputs a Ruby representation of the object β invaluable for debugging.
{{ page | inspect }}
sample
Returns a random element from an array.
{{ site.themes | sample }}
number_of_words
Counts words in a string.
{{ content | number_of_words }}
Chaining filters
Filters can be chained β output from one becomes input to the next.
{{ page.title | downcase | replace: " ", "-" | prepend: "/blog/" }}
{{ post.content | strip_html | truncatewords: 30 | append: "β¦" }}
Quick reference card
| Filter | What it does |
|---|---|
append / prepend |
Add text before or after |
upcase / downcase |
Change case |
strip_html |
Remove HTML tags |
truncate / truncatewords |
Shorten text |
replace |
Find and replace |
date |
Format a date |
where |
Filter array by property |
map |
Extract one property from array |
sort |
Sort an array |
size |
Count items |
default |
Fallback value |
relative_url |
Prefix baseurl |
jsonify |
Convert to JSON |
markdownify |
Render Markdown |
Bookmark this page β you will reference it constantly.
Understanding how filters work
Every Liquid filter takes an input and returns a transformed output. The pipe character (|) passes the left-hand value to the right-hand filter. Filters can be chained indefinitely β the output of each filter becomes the input to the next.
The most important thing to understand about filters is that they are non-destructive: they do not modify the original variable. {{ page.title | downcase }} outputs a lowercase version without changing page.title. This means you can safely apply filters in output tags without worrying about side effects on the underlying data.
When multiple filters are chained, Jekyll processes them left to right. {{ post.content | strip_html | truncatewords: 50 | append: "β¦" }} first strips HTML tags, then truncates to 50 words, then appends an ellipsis. The order matters β applying truncatewords before strip_html would include HTML tags in the word count and potentially cut a tag in half, producing malformed output.
Filters for generating meta content
Some of the most practical filter combinations in Jekyll are used to generate metadata β the content that lives in your <head> and affects search engine appearance and social sharing.
Generating a meta description
<meta name="description" content="{{ page.description | default: page.excerpt | strip_html | strip | truncate: 160 }}">
This chain: uses page.description if set, falls back to the first paragraph of the post via page.excerpt, strips any HTML tags, strips leading/trailing whitespace, and truncates to 160 characters. All four filters are necessary for robust output.
Generating Open Graph image URLs
<meta property="og:image" content="{{ page.image | default: site.og_image | absolute_url }}">
absolute_url is critical for OG images β relative URLs do not work in Open Graph meta tags. Twitter, Facebook, and other platforms require fully-qualified URLs including the domain.
Generating structured data
<script type="application/ld+json">
{
"headline": {{ page.title | jsonify }},
"datePublished": "{{ page.date | date_to_xmlschema }}",
"author": {{ page.author | default: site.author | jsonify }}
}
</script>
jsonify safely handles special characters in the title (quotes, apostrophes) by producing a properly-escaped JSON string. date_to_xmlschema produces the ISO 8601 format required by Schema.org.
Filters for navigation and archive pages
Building navigation, category archives, and tag clouds all rely heavily on array filters.
Category archive with post counts
{% assign categories = site.posts | map: "categories" | flatten | uniq | sort %}
{% for category in categories %}
{% assign count = site.posts | where_exp: "p", "p.categories contains category" | size %}
<a href="/category/{{ category | slugify }}/">
{{ category }} ({{ count }})
</a>
{% endfor %}
This uses map to extract categories from all posts, flatten to turn the nested arrays into a single list, uniq to remove duplicates, and sort to alphabetise them. The where_exp filter then counts posts in each category for the badge.
Tag cloud by frequency
{% assign tags = site.posts | map: "tags" | flatten | uniq | sort %}
{% for tag in tags %}
{% assign tag_posts = site.posts | where_exp: "p", "p.tags contains tag" %}
<span class="tag tag--{{ tag_posts | size }}">
<a href="/tag/{{ tag | slugify }}/">{{ tag }}</a>
</span>
{% endfor %}
Combine this with CSS that styles .tag--1 through .tag--10 at increasing font sizes to create a visual tag cloud where more popular tags appear larger.
Filters for date display
Jekyllβs date filter with strftime syntax is versatile but requires memorising format codes. The most common patterns for blog posts:
<!-- "July 3, 2026" -->
{{ page.date | date: "%B %-d, %Y" }}
<!-- "03 Jul 2026" (European format) -->
{{ page.date | date: "%d %b %Y" }}
<!-- "July 2026" (for archive page headings) -->
{{ page.date | date: "%B %Y" }}
<!-- ISO 8601 for <time> datetime attribute -->
<time datetime="{{ page.date | date_to_xmlschema }}">
{{ page.date | date: "%B %-d, %Y" }}
</time>
<!-- Relative human-readable: "3 days ago" β requires JavaScript; -->
<!-- In Liquid: just format the raw date, JS handles the relative part -->
For archive pages grouped by month, group_by_exp with a date filter produces clean groupings:
{% assign posts_by_month = site.posts | group_by_exp: "post", "post.date | date: '%B %Y'" %}
{% for month in posts_by_month %}
<h2>{{ month.name }}</h2>
{% for post in month.items %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
{% endfor %}
Filters for content processing
When rendering post content or custom fields in layouts, several filters handle the conversion from Markdown and raw text to safe, formatted HTML.
markdownify for front matter fields
<!-- _config.yml -->
author:
bio: "Writing about **Jekyll** and static sites. [More about me](/about/)."
<!-- In layout -->
<p>{{ site.author.bio | markdownify }}</p>
markdownify converts the Markdown bio (including the bold text and link) to HTML without you having to write HTML in your _config.yml.
xml_escape for RSS feeds
In your feed.xml template, always escape content before placing it inside XML elements:
<title>{{ post.title | xml_escape }}</title>
<description>{{ post.excerpt | strip_html | xml_escape }}</description>
xml_escape converts <, >, &, ", and ' to their XML entities, preventing malformed RSS feeds when post titles contain these characters.
Practical filter combinations
These are the filter chains you will find yourself writing repeatedly on real Jekyll projects.
Post cards with safe excerpts:
{{ post.excerpt | strip_html | strip | truncatewords: 25 }}
URL-safe anchor IDs from headings:
{{ heading | downcase | replace: " ", "-" | remove: "'" | remove: "," }}
Reading time estimate:
{% assign words = post.content | number_of_words %}
{% assign minutes = words | divided_by: 200 | at_least: 1 %}
{{ minutes }} min read
Conditional class from front matter:
<article class="post{% if page.featured %} post--featured{% endif %} post--{{ page.category | slugify }}">
Safe JSON for JavaScript:
<script>
const siteData = {
themes: {{ site.themes | jsonify }},
posts: {{ site.posts | map: "title" | jsonify }},
totalThemes: {{ site.themes | size }}
};
</script>
Liquid filters are one of the most expressive parts of Jekyll templating. The combination of string, array, date, and URL filters covers virtually every data transformation you will need in a Jekyll template. When you find yourself wanting to write JavaScript to transform data that already exists in your Jekyll templates, check whether a Liquid filter chain can do the same job at build time β the result is faster, simpler, and works without JavaScript.
Filters that are commonly misunderstood
Several Liquid filters behave slightly differently than their names suggest. Understanding these prevents subtle bugs in your templates.
truncate counts characters, not words. {{ "Hello world" | truncate: 5 }} outputs "He..." β not five words, but five characters including the ellipsis. Use truncatewords when you need a word-based limit.
divided_by does integer division by default. {{ 10 | divided_by: 3 }} outputs 3, not 3.333. To get decimal division, use floats: {{ 10.0 | divided_by: 3 }} outputs 3.3333333. This matters when calculating percentages or ratios.
sort is case-sensitive and puts uppercase before lowercase. {{ array | sort }} sorts ["Zebra", "apple", "Mango"] as ["Mango", "Zebra", "apple"]. Use sort_natural for case-insensitive alphabetical sorting.
where requires an exact match. {{ site.posts | where: "category", "Tutorial" }} returns only posts where category is exactly "Tutorial". It will not match "tutorial" (different case) or "Tutorials" (different value). Use where_exp for partial matches or case-insensitive filtering.
strip_html does not decode HTML entities. If your content contains &, strip_html leaves it as-is. Chain with remove or handle entity decoding separately if you need clean plain text.
jsonify adds surrounding quotes to strings. {{ "hello" | jsonify }} outputs "hello" (with quotes). When embedding in a JavaScript string context, this is correct. When embedding as an attribute value, you typically want the value without quotes β use {{ variable }} directly.
default only triggers on nil, false, or empty string. {{ 0 | default: "fallback" }} outputs 0 β zero is not nil. {{ false | default: "fallback" }} outputs "fallback" β false triggers the default. Understanding this distinction prevents logic errors when working with boolean front matter fields.
date requires a DateTime object, not a string. {{ "2026-01-01" | date: "%B %-d" }} may not work as expected β it tries to parse the string as a date. Use page.date (which Jekyll provides as a DateTime object) instead of a plain date string for reliable formatting.
Building reusable filter chains with include variables
Because Liquid filter chains can become long and repetitive, you can extract commonly used combinations into includes and pass them as parameters.
However, Liquid does not support custom function definitions. The include pattern is the closest equivalent: create an include file that processes its arguments and outputs the result.
<!-- _includes/safe-excerpt.html -->
{{ include.text | strip_html | strip | truncatewords: include.words | append: "β¦" }}
<!-- Usage -->
{% include safe-excerpt.html text=post.content words=30 %}
This avoids repeating the strip_html | strip | truncatewords | append chain in every card template. The include encapsulates the transformation, and you change it in one place if the excerpt format changes.
The same pattern works for other complex filter chains β schema date formatting, slug generation, and conditional URL building are all good candidates for extraction into includes when you find yourself writing the same chain in three or more templates.
Understanding filters at this depth separates Jekyll templates that are defensive and maintainable from ones that produce subtle output bugs under edge case inputs. Build the habit of thinking through what happens when a field is empty, null, or contains special characters β filters let you handle all these cases cleanly within the template itself.
Building filter literacy over time
The most effective way to get comfortable with Liquid filters is to read templates from well-maintained Jekyll themes and notice how they chain filters to handle real-world output requirements. Every time you see a filter you do not recognise, look it up in the Jekyll documentation. Over several projects, you will build fluency that makes template debugging fast and template writing straightforward. Filters are the vocabulary of Liquid β the more you know, the more expressively you can write templates that do exactly what you need.