Skip to main content

Liquid Filters

Filters modify the output of variables. They are applied using the pipe character |.

{{ "hello world" | capitalize | append: "!" }}
<!-- Output: Hello world! -->
Standard Liquid

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>

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">
&larr; {{ prev.title }}
</a>
{% endif %}

{% if next %}
<a href="{{ next.url }}" class="nav-next">
{{ next.title }} &rarr;
</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 -->
PresetSizeMode
thumb100x100Fit within
thumbnail150x150Fit within
small320x240Fit within
medium640x480Fit within
large1024x768Fit within
hero1920x1080Fit within
og1200x630Fit within
square400x400Crop to fill
avatar100x100Crop 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:

OptionTypeDefaultDescription
textstring-Watermark text (required)
positionstring-Placement: top-left, top-right, bottom-left, bottom-right
opacityinteger-Opacity (0-100)
sizeinteger-Font size
colorstring-Hex color (e.g., #ffffff)
shadowboolean-Add drop shadow
margininteger-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:

PresetOutput
shortJan 15
mediumJanuary 15, 2025
longWednesday, January 15, 2025
fullWednesday, January 15, 2025 at 03:30 PM
iso2025-01-15
time03:30 PM
time_2415:30
datetime2025-01-15 15:30
rssWed, 15 Jan 2025 15:30:00 +0000
year2025
monthJanuary 2025
day15

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 -->

Work with link settings from section schemas.

Extract URL from link setting.

<a href="{{ section.settings.button | link_href }}">
Click here
</a>

Extract text from link setting.

<a href="{{ section.settings.button | link_href }}">
{{ section.settings.button | link_text }}
</a>

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 }}

  • 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