Liquid Filters
Filters modify the output of variables. They are applied using the pipe character |.
{{ "hello world" | capitalize | append: "!" }}
<!-- Output: Hello world! -->
OQO extends Liquid, the templating language created by Shopify. All standard Liquid filters work in OQO themes, plus the custom filters documented below.
URL Filters
Generate URLs for models and assets.
tag_url
Generate URL for a tag.
<a href="{{ tag | tag_url }}">{{ tag.name }}</a>
<!-- Output: /tag/technology -->
<!-- In a loop -->
{% for tag in post.tags %}
<a href="{{ tag | tag_url }}">{{ tag.name }}</a>
{% endfor %}
collection_url
Generate URL for a collection.
<a href="{{ collection | collection_url }}">{{ collection.title }}</a>
<!-- Output: /collection/featured-stories -->
user_url
Generate URL for a user profile.
<a href="{{ post.author | user_url }}">{{ post.author.name }}</a>
<!-- Output: /author/john-doe -->
absolute_url
Convert relative path to absolute URL.
{{ post.url | absolute_url }}
<!-- Output: https://yoursite.com/post/my-article -->
asset_url
Generate URL for a non-image asset (PDF, file, etc.).
{{ 'styles.css' | asset_url }}
{{ section.settings.pdf_file | asset_url }}
download_url
Generate download URL that forces browser download.
<a href="{{ section.settings.file | download_url }}">Download PDF</a>
Navigation Filters
Filters for navigating between posts within a collection. Useful for building prev/next post navigation.
next_post
Get the next post in a collection (ordered by publication date).
{% assign next = collection | next_post: post %}
{% if next %}
<a href="{{ next.url }}">Next: {{ next.title }}</a>
{% endif %}
prev_post
Get the previous post in a collection.
{% assign prev = collection | prev_post: post %}
{% if prev %}
<a href="{{ prev.url }}">Previous: {{ prev.title }}</a>
{% endif %}
first_post
Get the first post in a collection.
{% assign first = collection | first_post %}
{% if first %}
<a href="{{ first.url }}">Start reading</a>
{% endif %}
last_post
Get the last post in a collection.
{% assign last = collection | last_post %}
{% if last %}
<a href="{{ last.url }}">Latest post</a>
{% endif %}
Complete Prev/Next Navigation Example
Build a full post navigation bar for a post detail page:
{% if collection %}
<nav class="post-navigation">
{% assign prev = collection | prev_post: post %}
{% assign next = collection | next_post: post %}
{% if prev %}
<a href="{{ prev.url }}" class="nav-prev">
← {{ prev.title }}
</a>
{% endif %}
{% if next %}
<a href="{{ next.url }}" class="nav-next">
{{ next.title }} →
</a>
{% endif %}
</nav>
{% endif %}
Image Filters
All image filters work with ImageDrop objects from posts, collections, users, elements, or settings. The image_url filter is the core of the image system -- it handles resizing, cropping, format conversion, and advanced effects like grayscale, duotone, watermarks, and film grain, all server-side via libvips.
image_url
Get a URL for an image with a specific size. Accepts preset names, custom geometry strings, or hash options.
Presets
{{ post.image | image_url: 'thumb' }} <!-- 100x100 -->
{{ post.image | image_url: 'small' }} <!-- 320x240 -->
{{ post.image | image_url: 'medium' }} <!-- 640x480 -->
{{ post.image | image_url: 'large' }} <!-- 1024x768 -->
{{ post.image | image_url: 'hero' }} <!-- 1920x1080 -->
{{ post.image | image_url: 'avatar' }} <!-- 100x100 cropped -->
{{ post.image | image_url: 'square' }} <!-- 400x400 cropped -->
{{ post.image | image_url: 'og' }} <!-- 1200x630 for social -->
| Preset | Size | Mode |
|---|---|---|
thumb | 100x100 | Fit within |
thumbnail | 150x150 | Fit within |
small | 320x240 | Fit within |
medium | 640x480 | Fit within |
large | 1024x768 | Fit within |
hero | 1920x1080 | Fit within |
og | 1200x630 | Fit within |
square | 400x400 | Crop to fill |
avatar | 100x100 | Crop to fill |
Custom Geometry (Legacy)
{{ post.image | image_url: '400x300' }} <!-- Fit within bounds -->
{{ post.image | image_url: '400x300#' }} <!-- Crop to exact size -->
{{ post.image | image_url: '400x300>' }} <!-- Only shrink if larger -->
{{ post.image | image_url: '400x' }} <!-- Width only, auto height -->
Hash Options
For full control, pass a hash with named options:
<!-- Resize -->
{{ post.image | image_url: width: 400 }}
{{ post.image | image_url: width: 400, height: 300 }}
<!-- Crop with gravity -->
{{ post.image | image_url: width: 400, height: 300, crop: true }}
{{ post.image | image_url: width: 400, height: 300, crop: 'north' }}
<!-- Format and quality -->
{{ post.image | image_url: width: 800, format: 'webp' }}
{{ post.image | image_url: width: 800, quality: 85 }}
Crop gravity options: center (default), north, south, east, west, north_west, north_east, south_west, south_east
Baseline Grid Alignment
Snap image heights to a typographic baseline grid. Calculates the proportional height, rounds up to the nearest baseline multiple, and crops to that exact height.
{{ post.image | image_url: width: 600, baseline: 24 }}
A 600x400 image with a 24px baseline would get height = ceil(400/24) * 24 = 408px, then crop to 600x408.
Image Effects
Server-side image effects powered by libvips. Combine with resize options:
<!-- Grayscale -->
{{ post.image | image_url: width: 800, grayscale: true }}
<!-- Sepia (warm brown tint) -->
{{ post.image | image_url: width: 800, sepia: true }}
<!-- Duotone (maps grayscale to two colors) -->
{{ post.image | image_url: width: 800, duotone_dark: '#1a1a2e', duotone_light: '#edf2f4' }}
<!-- Tint / monochrome (colorize with a single color) -->
{{ post.image | image_url: width: 800, tint: '#3b82f6' }}
<!-- Blur -->
{{ post.image | image_url: width: 800, blur: 5.0 }}
<!-- Sharpen / harden (sharpen + contrast boost) -->
{{ post.image | image_url: width: 800, harden: 50 }}
<!-- Soften (gentle blur) -->
{{ post.image | image_url: width: 800, soften: 30 }}
<!-- Film grain -->
{{ post.image | image_url: width: 800, grain: 50 }}
<!-- Remove background color (make a color transparent) -->
{{ post.image | image_url: color_to_alpha: '#ffffff' }}
{{ post.image | image_url: color_to_alpha: '#000000' }}
Watermark
Add a text watermark overlay to images:
<!-- Simple text watermark -->
{{ post.image | image_url: width: 800, watermark: '© My Brand' }}
For more control, use hash options:
{{ post.image | image_url: width: 800,
watermark: { text: '© My Brand', position: 'bottom-right', opacity: 30 } }}
Watermark options:
| Option | Type | Default | Description |
|---|---|---|---|
text | string | - | Watermark text (required) |
position | string | - | Placement: top-left, top-right, bottom-left, bottom-right |
opacity | integer | - | Opacity (0-100) |
size | integer | - | Font size |
color | string | - | Hex color (e.g., #ffffff) |
shadow | boolean | - | Add drop shadow |
margin | integer | - | Distance from edge in pixels |
Works with Any Image Source
{{ post.image | image_url: 'medium' }}
{{ collection.image | image_url: 'hero' }}
{{ post.author.image | image_url: 'avatar' }}
{{ section.settings.background_image | image_url: 'hero' }}
The filter handles ImageDrop objects, ActiveStorage attachments, Asset IDs (from section settings), and raw URLs.
image_srcset
Generate a srcset attribute for responsive images. Automatically creates URLs at small (320w), medium (640w), large (1024w), and hero (1920w) sizes.
<img src="{{ post.image | image_url: 'medium' }}"
srcset="{{ post.image | image_srcset }}"
sizes="(max-width: 640px) 100vw, 50vw"
alt="{{ post.image.alt | default: post.title }}">
image_tag
Generate a complete <img> tag with loading="lazy" automatically included.
{{ post.image | image_tag: 'medium', 'Alt text', 'my-class' }}
<!-- Output: <img src="..." alt="Alt text" class="my-class" loading="lazy"> -->
Parameters: image_tag: preset_or_options, alt_text, css_class
Color Filters
Shopify-style color manipulation for dynamic theming.
color_lighten
Lighten a color by percentage.
{{ '#333333' | color_lighten: 20 }}
<!-- Output: #666666 (lighter) -->
color_darken
Darken a color by percentage.
{{ '#ffffff' | color_darken: 15 }}
<!-- Output: #d9d9d9 (darker) -->
color_saturate / color_desaturate
Adjust color saturation.
{{ theme.settings.primary_color | color_saturate: 30 }}
{{ theme.settings.primary_color | color_desaturate: 30 }}
color_mix
Mix two colors together.
{{ '#ff0000' | color_mix: '#0000ff', 50 }}
<!-- Output: purple (50% red, 50% blue) -->
color_brightness
Get perceived brightness value (0-255).
{{ section.settings.bg_color | color_brightness }}
<!-- Output: 128 (mid-range) -->
color_brightness_class
Returns 'light' or 'dark' based on brightness.
<div class="bg-{{ section.settings.bg_color | color_brightness_class }}">
<!-- Adds class="bg-light" or class="bg-dark" -->
</div>
color_contrast
Get contrasting text color (black or white).
<span style="color: {{ section.settings.bg_color | color_contrast }}">
Readable text on any background
</span>
color_to_rgba
Convert hex color to rgba() CSS value.
{{ '#ff0000' | color_to_rgba: 0.5 }}
<!-- Output: rgba(255, 0, 0, 0.5) -->
color_to_hsl
Convert hex color to hsl() CSS value.
{{ '#ff0000' | color_to_hsl }}
<!-- Output: hsl(0, 100%, 50%) -->
color_alpha_to_rgba
Convert color_alpha setting (color + opacity) to rgba().
{{ section.settings.overlay_color | color_alpha_to_rgba }}
<!-- Input: {"color": "#000000", "alpha": 50} -->
<!-- Output: rgba(0, 0, 0, 0.5) -->
Usage in CSS variables:
{% capture section_style %}
--overlay-color: {{ section.settings.overlay | color_alpha_to_rgba }};
--bg-color: {{ section.settings.bg_color | color_to_rgba: 0.9 }};
{% endcapture %}
{% section_wrapper 'section' style: section_style %}
<div style="background: var(--overlay-color);">...</div>
{% endsection_wrapper %}
Text Filters
Text manipulation and formatting.
pluralize
Pluralize a word based on count (includes count in output).
{{ 1 | pluralize: 'item' }}
<!-- Output: 1 item -->
{{ 5 | pluralize: 'item' }}
<!-- Output: 5 items -->
{{ 3 | pluralize: 'category', 'categories' }}
<!-- Output: 3 categories -->
pluralize_word
Pluralize without including the count.
{{ collection.posts_count }} {{ collection.posts_count | pluralize_word: 'post' }}
<!-- Output: 12 posts -->
excerpt
Extract plain text excerpt from HTML content.
{{ post.content | excerpt: 200 }}
{{ post.content | excerpt: 100, '...' }}
titlecase
Convert to title case.
{{ 'hello world' | titlecase }}
<!-- Output: Hello World -->
sentencecase
Convert to sentence case.
{{ 'hello world' | sentencecase }}
<!-- Output: Hello world -->
highlight
Highlight search terms in text.
{{ post.title | highlight: 'search term' }}
<!-- Output: My <mark>Search Term</mark> Article -->
{{ post.title | highlight: 'term', 'strong' }}
<!-- Output: My <strong>term</strong> Article -->
parameterize
Convert text to URL-safe slug.
{{ 'Hello World!' | parameterize }}
<!-- Output: hello-world -->
Date Filters
Date formatting and relative time display.
date_format
Format a date with presets or custom format.
{{ post.published_at | date_format: 'short' }}
<!-- Output: Jan 15 -->
{{ post.published_at | date_format: 'medium' }}
<!-- Output: January 15, 2025 -->
{{ post.published_at | date_format: 'long' }}
<!-- Output: Wednesday, January 15, 2025 -->
{{ post.published_at | date_format: 'iso' }}
<!-- Output: 2025-01-15 -->
{{ post.published_at | date_format: '%B %Y' }}
<!-- Custom strftime format -->
Date Format Presets:
| Preset | Output |
|---|---|
short | Jan 15 |
medium | January 15, 2025 |
long | Wednesday, January 15, 2025 |
full | Wednesday, January 15, 2025 at 03:30 PM |
iso | 2025-01-15 |
time | 03:30 PM |
time_24 | 15:30 |
datetime | 2025-01-15 15:30 |
rss | Wed, 15 Jan 2025 15:30:00 +0000 |
year | 2025 |
month | January 2025 |
day | 15 |
time_ago
Get relative time description.
{{ post.published_at | time_ago }}
<!-- Output: 2 days ago, about 1 hour ago, just now, etc. -->
relative_date
Get relative date with smart formatting.
{{ post.published_at | relative_date }}
<!-- Output: Today, Yesterday, 3 days ago, or January 15, 2025 -->
past? / future?
Check if date is in past or future.
{% if event.date | past? %}
<span class="badge">Event has passed</span>
{% endif %}
{% if event.date | future? %}
<span class="badge">Upcoming</span>
{% endif %}
year / month_name
Extract date parts.
{{ post.published_at | year }}
<!-- Output: 2025 -->
{{ post.published_at | month_name }}
<!-- Output: January -->
Link Filters
Work with link settings from section schemas.
link_href
Extract URL from link setting.
<a href="{{ section.settings.button | link_href }}">
Click here
</a>
link_text
Extract text from link setting.
<a href="{{ section.settings.button | link_href }}">
{{ section.settings.button | link_text }}
</a>
link_target
Get target attribute for link.
<a href="{{ section.settings.link | link_href }}"
target="{{ section.settings.link | link_target }}">
{{ section.settings.link | link_text }}
</a>
opens_new_window
Check if link opens in new window.
<a href="{{ section.settings.link | link_href }}"
{% if section.settings.link | opens_new_window %}target="_blank" rel="noopener"{% endif %}>
{{ section.settings.link | link_text }}
</a>
Utility Filters
Common utilities for settings and CSS.
true? / false?
Boolean checks for non-boolean setting values (strings, objects).
<!-- For boolean settings (checkboxes), use natural boolean checks -->
{% if section.settings.show_title %}
<h1>{{ section.settings.title }}</h1>
{% endif %}
{% unless section.settings.hide_image %}
<img src="{{ post.image | image_url: 'medium' }}">
{% endunless %}
<!-- These filters are for checking string/object values -->
{% if some_string_value | true? %}
<!-- Checks if string equals "true" -->
{% endif %}
Note: Checkbox settings now return raw true/false values, so you can use natural boolean checks (if, unless). The .true? and .false? filters are still available for checking string values.
setting_value
Extract raw value from setting object.
{{ section.settings.theme | setting_value }}
background_style
Build CSS background-image style.
<div style="{{ section.settings.image | image_url: 'hero' | background_style }}">
</div>
<!-- Output: background-image: url(...); -->
gradient_overlay
Build CSS gradient overlay style.
<div style="{{ section.settings.color1 | gradient_overlay: section.settings.color2 }}">
</div>
add_class
Join CSS classes together, filtering out blanks.
{% assign base_class = 'card' %}
{% assign extra_class = 'featured' %}
<div class="{{ base_class | add_class: extra_class }}">
<!-- Output: class="card featured" -->
if_true
Return a value when the input is truthy, empty string otherwise.
<div class="article {{ section.settings.featured | if_true: 'is-featured' }}">
<!-- Output: class="article is-featured" (if featured is true) -->
<div class="grid {{ section.settings.flipped | if_true: 'order-2' }}">
append_if
Append a suffix to a string only when a condition is truthy. More general than if_true -- works with any string, not just CSS classes.
{%- assign section_class = 'relative' | append_if: section.settings.edge_to_edge, ' gz-section-bleed' -%}
<div class="{{ section_class }}">
{{ base_url | append_if: section.settings.show_anchor, '#signup' }}
Filter Chaining
Filters can be chained together for powerful transformations:
<!-- Responsive image with fallback alt -->
<img src="{{ post.image | image_url: 'medium' }}"
srcset="{{ post.image | image_srcset }}"
alt="{{ post.image.alt | default: post.title | escape }}">
<!-- Excerpt with word limit -->
{{ post.content | strip_html | truncate_words: 30 }}
<!-- Dynamic text color based on background -->
<div style="
background: {{ section.settings.bg_color }};
color: {{ section.settings.bg_color | color_contrast }};
">
<!-- Relative date with fallback -->
<time>{{ post.published_at | relative_date | default: 'Unpublished' }}</time>
<!-- Conditional class with color-based modifier -->
<section class="hero bg-{{ section.settings.bg_color | color_brightness_class }}">
Standard Liquid Filters
These commonly-used filters are built into Liquid:
String Filters
{{ "hello" | capitalize }} <!-- Hello -->
{{ "HELLO" | downcase }} <!-- hello -->
{{ "hello" | upcase }} <!-- HELLO -->
{{ "hello world" | truncate: 8 }} <!-- hello... -->
{{ " hello " | strip }} <!-- hello -->
{{ "hello" | append: " world" }} <!-- hello world -->
{{ "hello" | prepend: "say " }} <!-- say hello -->
{{ "hello" | replace: "l", "L" }} <!-- heLLo -->
Array Filters
{{ array | first }} <!-- First item -->
{{ array | last }} <!-- Last item -->
{{ array | size }} <!-- Number of items -->
{{ array | join: ", " }} <!-- Comma-separated -->
{{ array | sort }} <!-- Sorted -->
{{ array | reverse }} <!-- Reversed -->
{{ array | uniq }} <!-- Unique only -->
{{ posts | map: 'title' }} <!-- Extract property -->
{{ posts | where: 'featured' }} <!-- Filter by property -->
Number Filters
{{ 4.5 | ceil }} <!-- 5 -->
{{ 4.5 | floor }} <!-- 4 -->
{{ 4.5 | round }} <!-- 5 -->
{{ 1000 | plus: 500 }} <!-- 1500 -->
{{ 1000 | minus: 500 }} <!-- 500 -->
Date Filter
{{ post.published_at | date: "%B %d, %Y" }} <!-- January 15, 2025 -->
{{ post.published_at | date: "%Y-%m-%d" }} <!-- 2025-01-15 -->
Default Filter
{{ section.settings.title | default: 'Untitled' }}
{{ post.excerpt | default: post.title }}
Related Documentation
- Liquid Objects - Data objects like
post,collection,section,member - Liquid Tags - Control flow and custom tags including
zone,extends,style,post - Settings System - Defining section and theme settings