Skip to content

Twig Filters Reference

Total CMS extends Twig with powerful custom filters for text processing, color manipulation, array handling, and more. This reference includes usage examples for each filter.

humanize(string $slug, string $sep = '-'): string

Section titled “humanize(string $slug, string $sep = '-'): string”

Converts slugs to human-readable text.

{{ "hello-world" | humanize }}
{# Output: Hello World #}
{{ "first_name" | humanize('_') }}
{# Output: First Name #}

titleize(string $slug, string $sep = '-'): string

Section titled “titleize(string $slug, string $sep = '-'): string”

Converts to title case with proper capitalization.

{{ "the-quick-brown-fox" | titleize }}
{# Output: The Quick Brown Fox #}
{{ "api_response_data" | titleize('_') }}
{# Output: API Response Data #}

Extracts filename from path.

{{ "/path/to/document.pdf" | basename }}
{# Output: document.pdf #}
{% set upload = cms.file('manual') %}
<p>Download: {{ upload.file | basename }}</p>

Extracts directory from path.

{{ "/var/www/uploads/image.jpg" | dirname }}
{# Output: /var/www/uploads #}

Removes whitespace from both sides.

{{ " Hello World " | trim }}
{# Output: "Hello World" #}

Removes whitespace from the right side.

{{ " Hello World " | rtrim }}
{# Output: " Hello World" #}

Removes whitespace from the left side.

{{ " Hello World " | ltrim }}
{# Output: "Hello World " #}

Capitalizes the first letter of each word.

{{ "hello world" | ucwords }}
{# Output: "Hello World" #}

Lowercases the first character of a string.

{{ "Hello World" | lcfirst }}
{# Output: "hello World" #}

truncate(string $string, int $length, bool $keepWords = false): string

Section titled “truncate(string $string, int $length, bool $keepWords = false): string”

Truncates text to specified length.

{% set post = cms.collection.object('blog', 'my-post') %}
{# Character-based truncation #}
{{ post.content | truncate(100) }}
{# Word-safe truncation #}
{{ post.content | truncate(100, true) }}
{# Usage in blog listing #}
{% for post in cms.collection.objects('blog') %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt | truncate(150, true) }}...</p>
</article>
{% endfor %}

truncateWords(string $string, int $length): string

Section titled “truncateWords(string $string, int $length): string”

Truncates by word count instead of characters.

{{ post.content | truncateWords(25) }}
{# Truncates to first 25 words #}
{# Perfect for excerpts #}
<p class="excerpt">{{ post.content | truncateWords(30) }}...</p>

Counts characters in text.

{% set content = cms.text('about-us') %}
<p>About us ({{ content.text | charcount }} characters)</p>
{# Validation in forms #}
{% if post.title | charcount > 100 %}
<span class="error">Title too long</span>
{% endif %}

Counts words in text.

{{ post.content | wordcount }} words
{# Reading time calculation #}
{% set words = post.content | wordcount %}
<time>{{ (words / 200) | round }} min read</time>

readtime(string $text, int $wpm = 180): float

Section titled “readtime(string $text, int $wpm = 180): float”

Calculates reading time in minutes.

{{ post.content | readtime }} min read
{{ post.content | readtime(250) }} min read (fast reader)
{# Display reading time #}
<div class="meta">
<span>{{ post.content | readtime | round }} minute read</span>
</div>

Extracts only digits from text.

{{ "Phone: (555) 123-4567" | digitsOnly }}
{# Output: 5551234567 #}
{# Clean phone numbers for tel: links #}
<a href="tel:{{ contact.phone | digitsOnly }}">
{{ contact.phone | formatPhone }}
</a>

formatPhone(string $string, string $countryCode = 'US'): string

Section titled “formatPhone(string $string, string $countryCode = 'US'): string”

Formats phone numbers by country.

{{ "5551234567" | formatPhone }}
{# Output: (555) 123-4567 #}
{{ "5551234567" | formatPhone('CA') }}
{# Output: (555) 123-4567 #}
{# Format contact information #}
{% for contact in api('contact') %}
<p>{{ contact.name }}: {{ contact.phone | formatPhone }}</p>
{% endfor %}

prefixSlug(string|array $value, string $prefix = '', string $separator = ' '): string

Section titled “prefixSlug(string|array $value, string $prefix = '', string $separator = ' '): string”

Slugifies and optionally prefixes a string or array of strings. Useful for generating CSS classes, HTML attributes, or URL-safe identifiers from user content.

{# Basic array with prefix - great for tag CSS classes #}
{{ post.tags | prefixSlug('tag-') }}
{# Input: ['Web Design', 'UI/UX'] → Output: "tag-web-design tag-ui-ux" #}
{# Single string #}
{{ "Hello World" | prefixSlug('prefix-') }}
{# Output: "prefix-hello-world" #}
{# Custom separator #}
{{ post.tags | prefixSlug('tag-', ', ') }}
{# Input: ['php', 'javascript'] → Output: "tag-php, tag-javascript" #}
{# No prefix - just slugify and join #}
{{ post.tags | prefixSlug }}
{# Input: ['Web Design', 'UI/UX'] → Output: "web-design ui-ux" #}
{# Empty items are automatically filtered out #}
{{ ['foo', '', 'bar'] | prefixSlug('x-') }}
{# Output: "x-foo x-bar" #}

Parameters:

  • value - String or array of strings to process
  • prefix - String to prepend to each slugified item (default: '')
  • separator - String used to join items (default: ' ' space)

Real-World Examples:

{# Generate CSS classes for tags #}
<article class="{{ post.tags | prefixSlug('tag-') }}">
{{ post.content }}
</article>
{# Create data attributes #}
<div data-categories="{{ post.categories | prefixSlug('', ',') }}">
{# Filter controls #}
{% for tag in allTags %}
<button class="filter-btn" data-filter=".tag-{{ tag | prefixSlug }}">
{{ tag }}
</button>
{% endfor %}
{# Body class based on page sections #}
<body class="page-{{ page.id | prefixSlug }} {{ page.sections | prefixSlug('section-') }}">

svgToSymbol(string $svg, string $symbolId): string

Section titled “svgToSymbol(string $svg, string $symbolId): string”

Converts SVG to symbol for icon systems.

{% set icon = cms.svg('home-icon') %}
{{ icon.svg | svgToSymbol('home') }}
{# Create icon library #}
<svg style="display: none;">
{% for icon in cms.collection.objects('svg') %}
{{ icon.content | svgToSymbol(icon.id) }}
{% endfor %}
</svg>
{# Use icons #}
<svg><use href="#home"></use></svg>

Processes full markdown syntax including block elements (paragraphs, headers, lists, etc.).

{% set content = "This is **bold** and [a link](https://example.com)" %}
{{ content | markdown }}
{# Output: <p>This is <strong>bold</strong> and <a href="https://example.com">a link</a></p> #}
{# Process blog post content #}
<article>
{{ post.content | markdown }}
</article>
{# Markdown with tables and code blocks #}
{{ documentation.content | markdown }}

Features:

  • Full markdown syntax support (headers, lists, blockquotes, etc.)
  • GitHub Flavored Markdown (tables, fenced code blocks)
  • Extended features (footnotes, definition lists)
  • Wraps content in appropriate block elements (<p>, <h1>, etc.)

Processes only inline markdown syntax without wrapping content in <p> tags or other block elements. Perfect for processing markdown within existing HTML elements.

{% set text = "This is **bold** and [a link](https://example.com)" %}
{{ text | markdownInline }}
{# Output: This is <strong>bold</strong> and <a href="https://example.com">a link</a> #}
{# Use in headings #}
<h2>{{ page.title | markdownInline }}</h2>
{# Use in existing paragraphs #}
<p class="description">{{ product.tagline | markdownInline }}</p>
{# Use in list items #}
<ul>
{% for feature in product.features %}
<li>{{ feature | markdownInline }}</li>
{% endfor %}
</ul>

Supported Inline Syntax:

  • Bold: **text**<strong>text</strong>
  • Italic: *text*<em>text</em>
  • Code: `code`<code>code</code>
  • Links: [text](url)<a href="url">text</a>
  • Auto-links: <https://example.com><a href="...">...</a>
  • Images: ![alt](url)<img src="url" alt="alt" />
  • Combined styles: **bold** with *italic* works perfectly

When to Use:

  • Processing markdown within existing HTML tags
  • Headings, button labels, or navigation items with markdown
  • Form field labels or help text with formatting
  • List items or table cells with inline formatting
  • Meta descriptions or short text snippets

Comparison:

{# markdown filter - adds <p> wrapper #}
{{ "Visit our **website**" | markdown }}
{# Output: <p>Visit our <strong>website</strong></p> #}
{# markdownInline filter - no wrapper #}
{{ "Visit our **website**" | markdownInline }}
{# Output: Visit our <strong>website</strong> #}

Real-World Examples:

{# Product features with inline formatting #}
<ul class="features">
{% for feature in product.features %}
<li>{{ feature | markdownInline }}</li>
{# Input: "**High-speed** processing with *low power*" #}
{# Output: <strong>High-speed</strong> processing with <em>low power</em> #}
{% endfor %}
</ul>
{# Page titles with emphasis #}
<h1>{{ page.title | markdownInline }}</h1>
{# Input: "Welcome to our **Amazing** Store!" #}
{# Output: Welcome to our <strong>Amazing</strong> Store! #}
{# Navigation with inline links #}
<nav>
{% for item in menu %}
<a href="{{ item.url }}">{{ item.label | markdownInline }}</a>
{% endfor %}
</nav>
{# Meta descriptions #}
<meta name="description" content="{{ page.meta | markdownInline | striptags }}">
{# Testimonial quotes with formatting #}
<blockquote>
{{ testimonial.quote | markdownInline }}
<cite>{{ testimonial.author }}</cite>
</blockquote>

Security Note: Both markdown filters run in safe mode with XSS protection enabled.

Encodes text to HTML entities, useful for obfuscating email addresses from scrapers.

{{ "user@example.com" | htmlencode }}
{# Output: &#117;&#115;&#101;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109; #}
{# Useful for displaying emails without making them easily scrapable #}
<span>Contact: {{ contact.email | htmlencode }}</span>

Decodes HTML entities back to their original characters. Alias for html_entity_decode.

{{ "&amp;copy; 2024" | htmldecode }}
{# Output: © 2024 #}
{{ "&lt;div&gt;Hello&lt;/div&gt;" | htmldecode }}
{# Output: <div>Hello</div> #}
{# Useful when processing content from external sources #}
{{ externalContent.title | htmldecode }}

URL-encodes a string.

{{ "hello world" | urlencode }}
{# Output: hello+world #}
<a href="/search?q={{ searchTerm | urlencode }}">Search</a>

Decodes a URL-encoded string.

{{ "hello+world" | urldecode }}
{# Output: hello world #}

URL-encodes a string according to RFC 3986 (spaces become %20).

{{ "hello world" | rawurlencode }}
{# Output: hello%20world #}

Obfuscates a string for basic protection. Reversible with deobfuscate.

{{ "secret-data" | obfuscate }}

Reverses obfuscation applied by obfuscate.

{{ obfuscatedString | deobfuscate }}

Encrypts a string using the application’s encryption key.

{{ sensitiveData | encrypt }}

Decrypts a string that was encrypted with encrypt.

{{ encryptedData | decrypt }}

mailto(string $email, string $subject = '', string $body = '', string $title = ''): string

Section titled “mailto(string $email, string $subject = '', string $body = '', string $title = ''): string”

Creates an obfuscated mailto link that protects email addresses from spam bots. The email is not visible in the raw HTML source and is decoded via JavaScript.

{# Basic usage #}
{{ "info@example.com" | mailto }}
{# With subject #}
{{ "support@example.com" | mailto("Support Request") }}
{# With subject and body #}
{{ "sales@example.com" | mailto("Product Inquiry", "I'm interested in your products...") }}
{# With custom link title #}
{{ "contact@example.com" | mailto("", "", "Contact Our Team") }}
{# Real-world examples #}
<div class="contact-info">
<p>General inquiries: {{ site.email | mailto }}</p>
<p>Support: {{ "support@example.com" | mailto("Help needed") }}</p>
<p>{{ "hr@example.com" | mailto("Job Application", "Position: ", "Apply Now") }}</p>
</div>

How it works:

  • Email addresses are split and base64 encoded in data attributes
  • Displayed using HTML entities (e.g., &#64; for @)
  • JavaScript converts to real mailto links on page load
  • If JavaScript is disabled, the encoded email is still visible

Generated HTML:

<span class="mailto-obfuscated"
data-user="aW5mbw=="
data-domain="ZXhhbXBsZS5jb20="
title="Email"
style="cursor:pointer;text-decoration:underline;">
&#105;&#110;&#102;&#111;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;
</span>

Note: Requires content.js to be loaded on your pages for the JavaScript decoder to work.

Converts hex color to color array.

{% set brand = "#3498db" | hexToColor %}
{# Returns: {r: 52, g: 152, b: 219, h: 204, s: 70, l: 53} #}

Converts color array to hex.

{% set primary = cms.toggle('brand-color') %}
<style>
:root {
--primary: {{ primary.color | hex }};
}
</style>

rgb(array $color, int $alpha = 100, bool $wrap = true): string

Section titled “rgb(array $color, int $alpha = 100, bool $wrap = true): string”

Converts to RGB/RGBA format.

{% set primary = cms.color('primary') %}
background-color: {{ primary | rgb }};
background-color: {{ primary | rgb(50) }}; {# 50% opacity #}
background-color: {{ primary | rgb(75, false) }}; {# No wrapper #}

hsl(array $color, int $alpha = 100, bool $wrap = true): string

Section titled “hsl(array $color, int $alpha = 100, bool $wrap = true): string”

Converts to HSL/HSLA format.

{% set accent = cms.color('accent') | hsl %}
<div style="background: {{ accent }};">Content</div>
{# Semi-transparent overlay #}
<div style="background: {{ cms.color('accent') | hsl(20) }};">Overlay</div>

oklch(array $color, int $alpha = 100, bool $wrap = true): string

Section titled “oklch(array $color, int $alpha = 100, bool $wrap = true): string”

Converts to modern OKLCH format.

{# Modern color space for better gradients #}
background: {{ brand.primary | oklch }};

color(array $color, int $alpha = 100, bool $wrap = true): string

Section titled “color(array $color, int $alpha = 100, bool $wrap = true): string”

Alias for oklch. Converts to OKLCH format.

background: {{ brand.primary | color }};
background: {{ brand.primary | color(50) }}; {# 50% opacity #}

colour(array $color, int $alpha = 100, bool $wrap = true): string

Section titled “colour(array $color, int $alpha = 100, bool $wrap = true): string”

British spelling alias for oklch.

background: {{ brand.primary | colour }};

lightness(array $color, string $lightness): array

Section titled “lightness(array $color, string $lightness): array”

Adjusts color lightness.

{% set base = "#3498db" | hexToColor %}
{% set light = base | lightness('+20') %}
{% set dark = base | lightness('-20') %}
<style>
.button-primary { background: {{ base | hex }}; }
.button-primary:hover { background: {{ dark | hex }}; }
.button-primary:active { background: {{ light | hex }}; }
</style>

chroma(array $color, string $chroma): array

Section titled “chroma(array $color, string $chroma): array”

Adjusts color saturation/chroma.

{% set muted = brand.primary | chroma('-30') %}
<p style="color: {{ muted | hex }};">Muted text</p>

Adjusts color hue.

{% set complementary = brand.primary | hue('+180') %}
<div class="accent" style="border-color: {{ complementary | hex }};"></div>

adjustColor(array $color, ?string $lightness = null, ?string $chroma = null, ?string $hue = null): array

Section titled “adjustColor(array $color, ?string $lightness = null, ?string $chroma = null, ?string $hue = null): array”

Adjusts multiple color properties at once.

{% set variant = brand.primary | adjustColor('+10', '-20', '+30') %}

Counts array elements.

{% set posts = cms.collection.objects('blog') %}
<p>{{ posts | count }} blog posts available</p>
{# Conditional display #}
{% if gallery.images | count > 0 %}
<div class="gallery">...</div>
{% endif %}

Sorts array by keys.

{% set categories = cms.collection.objects('categories') | ksort %}
{% for category in categories %}
<option value="{{ category.id }}">{{ category.name }}</option>
{% endfor %}

Sorts array by keys in reverse order.

{% set years = posts | groupBy('year') | krsort %}
{% for year, yearPosts in years %}
<h3>{{ year }}</h3>
{# Posts for this year #}
{% endfor %}

Sorts an array of objects/arrays by a specific key.

{% set products = cms.collection.objects('products') | sortBy('price') %}
{% for product in products %}
<div>{{ product.name }} - ${{ product.price }}</div>
{% endfor %}
{# Sort users alphabetically by name #}
{% set users = cms.collection.objects('users') | sortBy('name') %}

Randomly shuffles array elements.

{% set testimonials = cms.collection.objects('testimonials') | shuffle | slice(0, 3) %}
{% for testimonial in testimonials %}
<blockquote>{{ testimonial.quote }}</blockquote>
{% endfor %}

paginate(array $array, int $limit, int $page = 1): array

Section titled “paginate(array $array, int $limit, int $page = 1): array”

Returns a paginated slice of an array.

{% set page = get.page | default(1) | int %}
{% set perPage = 10 %}
{% set posts = cms.collection.objects('blog') | paginate(perPage, page) %}
{% for post in posts %}
<article>{{ post.title }}</article>
{% endfor %}
{# Pagination with navigation #}
{% set allPosts = cms.collection.objects('blog') %}
{% set totalPages = (allPosts | length / perPage) | round(0, 'ceil') %}
<nav>
{% for p in 1..totalPages %}
<a href="?page={{ p }}" {{ p == page ? 'class="active"' : '' }}>{{ p }}</a>
{% endfor %}
</nav>

Removes duplicate values from an array.

{% set tags = ["php", "javascript", "php", "css", "javascript"] %}
{{ tags | unique | join(', ') }}
{# Output: php, javascript, css #}
{# Get unique categories from blog posts #}
{% set allCategories = [] %}
{% for post in cms.collection.objects('blog') %}
{% set allCategories = allCategories | merge([post.category]) %}
{% endfor %}
{% set uniqueCategories = allCategories | unique %}
{# Build category filter #}
<select name="category">
<option value="">All Categories</option>
{% for category in uniqueCategories %}
<option value="{{ category }}">{{ category | titleize }}</option>
{% endfor %}
</select>
{# Unique tags across all posts #}
{% set allTags = [] %}
{% for post in cms.collection.objects('blog') %}
{% set allTags = allTags | merge(post.tags) %}
{% endfor %}
{% set uniqueTags = allTags | unique | sort %}
<div class="tag-cloud">
{% for tag in uniqueTags %}
<a href="/blog?tag={{ tag | urlencode }}">{{ tag }}</a>
{% endfor %}
</div>
{# Remove duplicate authors #}
{% set authors = posts | map(p => p.author) | unique %}
<p>Authors: {{ authors | join(', ') }}</p>

These filters provide powerful ways to aggregate, transform, and analyze collections without writing verbose loops. They’re especially useful for calculating statistics, creating lookup tables, and organizing data.

sum(array $collection, string $property): float

Section titled “sum(array $collection, string $property): float”

Calculates the sum of a numeric property across all items in a collection.

{# Basic usage #}
{% set total = cms.collection.objects('orders') | sum('amount') %}
<p>Total sales: ${{ total | number_format(2) }}</p>
{# Calculate revenue #}
{% set revenue = cms.collection.objects('transactions') | sum('price') %}
{# Sum with filtering #}
{% set completedTotal = cms.collection.objects('orders')
| filterCollection([{property: 'status', operator: 'eq', value: 'completed'}])
| sum('amount') %}
{# Real-world example: Business totals #}
{% set bustot = cms.collection.objects('busamount') | sum('amt') %}
<p>Total business generated: ${{ bustot | number_format(0, '.', ',') }}</p>

avg(array $collection, string $property): float

Section titled “avg(array $collection, string $property): float”

Calculates the average of a numeric property across all items.

{# Basic usage #}
{% set avgPrice = cms.collection.objects('products') | avg('price') %}
<p>Average price: ${{ avgPrice | number_format(2) }}</p>
{# Calculate average rating #}
{% set avgRating = cms.collection.objects('reviews') | avg('rating') %}
<div class="rating">{{ avgRating | round(1) }} / 5 stars</div>
{# Average order value #}
{% set aov = cms.collection.objects('orders') | avg('total') %}
<p>Average order value: ${{ aov | number_format(2) }}</p>
{# Combine with sum for more stats #}
{% set orders = cms.collection.objects('orders') %}
<div class="stats">
<p>Total: ${{ orders | sum('amount') | number_format(2) }}</p>
<p>Average: ${{ orders | avg('amount') | number_format(2) }}</p>
<p>Count: {{ orders | length }}</p>
</div>

min(array $collection, string $property): float|null

Section titled “min(array $collection, string $property): float|null”

Finds the minimum value of a numeric property.

{# Basic usage #}
{% set lowestPrice = cms.collection.objects('products') | min('price') %}
<p>Starting at ${{ lowestPrice | number_format(2) }}</p>
{# Find earliest date (works with sortable date strings) #}
{% set firstPost = cms.collection.objects('blog') | min('date') %}
{# Price range display #}
{% set products = cms.collection.objects('products') %}
{% set minPrice = products | min('price') %}
{% set maxPrice = products | max('price') %}
<p>Prices range from ${{ minPrice }} to ${{ maxPrice }}</p>
{# Handle null (no valid values) #}
{% set lowest = products | min('sale_price') %}
{% if lowest is not null %}
<p>Sale prices from ${{ lowest }}</p>
{% endif %}

max(array $collection, string $property): float|null

Section titled “max(array $collection, string $property): float|null”

Finds the maximum value of a numeric property.

{# Basic usage #}
{% set highestPrice = cms.collection.objects('products') | max('price') %}
<p>Up to ${{ highestPrice | number_format(2) }}</p>
{# Find highest rated #}
{% set topRating = cms.collection.objects('reviews') | max('rating') %}
{# Display price range #}
{% set products = cms.collection.objects('products') %}
<p>
${{ products | min('price') | number_format(2) }} -
${{ products | max('price') | number_format(2) }}
</p>
{# Find largest file #}
{% set gallery = cms.render.gallery('portfolio') %}
{% set largestFile = gallery | max('size') %}
<p>Largest image: {{ largestFile | filesize }}</p>

pluck(array $collection, string $property): array

Section titled “pluck(array $collection, string $property): array”

Extracts a single property from all items, returning a flat array of values.

{# Basic usage - get all email addresses #}
{% set emails = cms.collection.objects('members') | pluck('email') %}
{# Result: ['john@example.com', 'jane@example.com', ...] #}
{# Get all IDs #}
{% set ids = cms.collection.objects('products') | pluck('id') %}
{# Create comma-separated list #}
{% set names = cms.collection.objects('team') | pluck('name') %}
<p>Team: {{ names | join(', ') }}</p>
{# Get all tags from posts (then flatten and unique) #}
{% set allTags = cms.collection.objects('blog') | pluck('tags') | flatten | unique %}
<div class="tag-cloud">
{% for tag in allTags %}
<a href="/blog?tag={{ tag | urlencode }}">{{ tag }}</a>
{% endfor %}
</div>
{# Get SKUs for inventory check #}
{% set skus = cms.collection.objects('products') | pluck('sku') %}
{# Use for API call or validation #}
{# Build select options #}
<select name="author">
{% for author in cms.collection.objects('authors') | pluck('name') %}
<option>{{ author }}</option>
{% endfor %}
</select>

keyBy(array $collection, string $property = 'id'): array

Section titled “keyBy(array $collection, string $property = 'id'): array”

Converts a collection into an associative array keyed by a specific property. This is extremely useful for creating lookup tables and avoiding N+1 query patterns.

{# Basic usage - create lookup table by ID #}
{% set memberLookup = cms.collection.objects('members') | keyBy %}
{# Same as: | keyBy('id') #}
{# Access by ID (O(1) lookup instead of loop) #}
{% set member = memberLookup['000042'] %}
<p>{{ member.fname }} {{ member.lname }}</p>
{# Key by different property #}
{% set productsBySku = cms.collection.objects('products') | keyBy('sku') %}
{% set product = productsBySku['ABC-123'] %}
{# Key by slug #}
{% set pagesBySlug = cms.collection.objects('pages') | keyBy('slug') %}
{% set aboutPage = pagesBySlug['about-us'] %}
{# IMPORTANT: Avoid N+1 pattern #}
{# BAD - calls cms.collection.object() 50 times: #}
{% for order in orders %}
{% set customer = cms.collection.object('customers', order.customer_id) %}
{% endfor %}
{# GOOD - calls cms.collection.objects() once, then O(1) lookups: #}
{% set customerLookup = cms.collection.objects('customers') | keyBy %}
{% for order in orders %}
{% set customer = customerLookup[order.customer_id] | default({}) %}
<p>{{ customer.name }} ordered {{ order.total | price }}</p>
{% endfor %}
{# Real-world example: Display referrals with member names #}
{% set memberLookup = cms.collection.objects('members') | keyBy %}
{% for referral in cms.collection.objects('referrals') | paginate(50, page) %}
{% set fromMember = memberLookup[referral.from_id] | default({}) %}
{% set toMember = memberLookup[referral.to_id] | default({}) %}
<tr>
<td>{{ fromMember.fname }} {{ fromMember.lname }}</td>
<td>{{ toMember.fname }} {{ toMember.lname }}</td>
<td>{{ referral.amount | price }}</td>
</tr>
{% endfor %}

Performance Note: keyBy is essential for avoiding the N+1 query problem. Instead of fetching related objects inside a loop (which causes many individual lookups), load all related objects once and use keyBy to create a lookup table.

groupBy(array $collection, string $property): array

Section titled “groupBy(array $collection, string $property): array”

Groups items by a property value, returning an associative array where keys are property values and values are arrays of items.

{# Basic usage - group posts by category #}
{% set postsByCategory = cms.collection.objects('blog') | groupBy('category') %}
{# Result: {news: [...], tutorials: [...], reviews: [...]} #}
{# Display grouped content #}
{% for category, posts in postsByCategory %}
<h2>{{ category | titleize }}</h2>
<ul>
{% for post in posts %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
{% endfor %}
{# Group products by brand #}
{% set productsByBrand = cms.collection.objects('products') | groupBy('brand') %}
{% for brand, products in productsByBrand %}
<section class="brand-section">
<h3>{{ brand }}</h3>
<p>{{ products | length }} products</p>
{% for product in products %}
<div class="product">{{ product.name }}</div>
{% endfor %}
</section>
{% endfor %}
{# Group events by month #}
{% set events = cms.collection.objects('events') %}
{% set eventsByMonth = events | groupBy('month') %}
{# Group orders by status #}
{% set ordersByStatus = cms.collection.objects('orders') | groupBy('status') %}
<div class="order-stats">
<p>Pending: {{ ordersByStatus.pending | length | default(0) }}</p>
<p>Completed: {{ ordersByStatus.completed | length | default(0) }}</p>
<p>Cancelled: {{ ordersByStatus.cancelled | length | default(0) }}</p>
</div>
{# Group team members by department #}
{% set teamByDept = cms.collection.objects('team') | groupBy('department') %}
{% for dept, members in teamByDept %}
<div class="department">
<h3>{{ dept }}</h3>
{% for member in members %}
<div class="member">{{ member.name }} - {{ member.title }}</div>
{% endfor %}
</div>
{% endfor %}

Note: Items with empty or missing property values are grouped under the key _ungrouped.

countBy(array $collection, string $property): array

Section titled “countBy(array $collection, string $property): array”

Counts items grouped by a property value. Like groupBy, but returns counts instead of the items themselves.

{# Basic usage - count posts per category #}
{% set postCounts = cms.collection.objects('blog') | countBy('category') %}
{# Result: {news: 5, tutorials: 12, reviews: 3} #}
{# Display category counts #}
<ul class="category-list">
{% for category, count in postCounts %}
<li>{{ category | titleize }}: {{ count }} posts</li>
{% endfor %}
</ul>
{# Count orders by status #}
{% set statusCounts = cms.collection.objects('orders') | countBy('status') %}
<div class="dashboard">
<div class="stat">
<span class="number">{{ statusCounts.pending | default(0) }}</span>
<span class="label">Pending</span>
</div>
<div class="stat">
<span class="number">{{ statusCounts.completed | default(0) }}</span>
<span class="label">Completed</span>
</div>
</div>
{# Count products by brand #}
{% set brandCounts = cms.collection.objects('products') | countBy('brand') %}
{% for brand, count in brandCounts | ksort %}
<option value="{{ brand }}">{{ brand }} ({{ count }})</option>
{% endfor %}
{# Count members by membership type #}
{% set membershipCounts = cms.collection.objects('members') | countBy('membership_type') %}
<table>
<tr><th>Membership</th><th>Count</th></tr>
{% for type, count in membershipCounts %}
<tr>
<td>{{ type | titleize }}</td>
<td>{{ count }}</td>
</tr>
{% endfor %}
</table>
{# Combine with other filters for insights #}
{% set activeMembers = cms.collection.objects('members')
| filterCollection([{property: 'active', operator: 'eq', value: true}]) %}
{% set byCity = activeMembers | countBy('city') %}
<h3>Active Members by City</h3>
{% for city, count in byCity | ksort %}
<p>{{ city }}: {{ count }}</p>
{% endfor %}

These filters can be chained together for powerful data analysis:

{# Get statistics for active products #}
{% set activeProducts = cms.collection.objects('products')
| filterCollection([{property: 'status', operator: 'eq', value: 'active'}]) %}
<div class="product-stats">
<p>Total products: {{ activeProducts | length }}</p>
<p>Total inventory value: ${{ activeProducts | sum('price') | number_format(2) }}</p>
<p>Average price: ${{ activeProducts | avg('price') | number_format(2) }}</p>
<p>Price range: ${{ activeProducts | min('price') }} - ${{ activeProducts | max('price') }}</p>
</div>
{# Dashboard with grouped statistics #}
{% set orders = cms.collection.objects('orders') %}
{% set byStatus = orders | groupBy('status') %}
<div class="order-dashboard">
{% for status, statusOrders in byStatus %}
<div class="status-card">
<h4>{{ status | titleize }}</h4>
<p>Count: {{ statusOrders | length }}</p>
<p>Total: ${{ statusOrders | sum('total') | number_format(2) }}</p>
<p>Average: ${{ statusOrders | avg('total') | number_format(2) }}</p>
</div>
{% endfor %}
</div>
{# Create a leaderboard #}
{% set members = cms.collection.objects('members') %}
{% set memberLookup = members | keyBy %}
{% set referrals = cms.collection.objects('referrals') | countBy('from_id') %}
<h2>Top Referrers</h2>
<ol>
{% for memberId, count in referrals | sort | reverse | slice(0, 10) %}
{% set member = memberLookup[memberId] | default({}) %}
<li>{{ member.fname }} {{ member.lname }}: {{ count }} referrals</li>
{% endfor %}
</ol>

Returns variable type.

{{ post.date | typeof }} {# string #}
{{ gallery.images | typeof }} {# array #}
{# Debug templates #}
{% if app.debug %}
<pre>{{ dump(variable) }} ({{ variable | typeof }})</pre>
{% endif %}

Converts to string.

{{ post.id | string }}
{{ user.age | string }}

Converts to integer.

{% set page = get.page | int | default(1) %}
{% set limit = get.limit | int | default(10) %}

Converts to float.

{% set price = product.price | float %}
<span class="price">${{ price | number_format(2) }}</span>

Converts to boolean.

{% set featured = post.featured | bool %}
{% if featured %}
<span class="badge">Featured</span>
{% endif %}

Converts to array.

{% set tags = post.tags | array %}
{% for tag in tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}

Decodes JSON string to array.

{% set config = post.metadata | json_decode %}
{% for key, value in config %}
<meta name="{{ key }}" content="{{ value }}">
{% endfor %}

Pretty-prints variable for debugging.

{% if app.debug %}
<pre>{{ post | print_r }}</pre>
{% endif %}

Detailed variable dump for debugging.

{% if app.debug %}
<pre>{{ complex_object | var_dump }}</pre>
{% endif %}

Total CMS includes powerful date manipulation filters powered by CakePHP Chronos. These filters support natural language date strings like “tomorrow”, “next monday”, “+1 week”, making date handling intuitive and flexible.

dateFormat(mixed $date, string $format = 'Y-m-d H:i:s'): string

Section titled “dateFormat(mixed $date, string $format = 'Y-m-d H:i:s'): string”

Formats dates with custom format strings.

{{ post.created_at | dateFormat }}
{# Output: 2024-06-15 14:30:00 #}
{{ post.created_at | dateFormat('F j, Y') }}
{# Output: June 15, 2024 #}
{{ post.created_at | dateFormat('l, F d, Y g:i A') }}
{# Output: Saturday, June 15, 2024 2:30 PM #}
{# Works with smart date strings #}
{{ "tomorrow" | dateFormat('Y-m-d') }}
{{ "+1 week" | dateFormat('F j, Y') }}

Returns human-readable relative date strings.

{{ post.updated_at | dateRelative }}
{# Output: 2 days ago #}
{{ event.date | dateRelative }}
{# Output: in 3 weeks #}
{# Perfect for social media style timestamps #}
{% for comment in post.comments %}
<time>{{ comment.created | dateRelative }}</time>
{% endfor %}

dateAdd(mixed $date, string $interval): string

Section titled “dateAdd(mixed $date, string $interval): string”

Adds time intervals to dates.

{{ "now" | dateAdd('+1 day') | dateFormat('Y-m-d') }}
{{ event.date | dateAdd('+2 weeks') | dateFormat('F j, Y') }}
{{ deadline | dateAdd('+3 hours') | dateFormat('g:i A') }}
{# Chain multiple additions #}
{{ "now" | dateAdd('+1 month') | dateAdd('+5 days') | dateFormat('Y-m-d') }}
{# Calculate future dates #}
{% set nextPayment = subscription.date | dateAdd('+1 month') %}

dateSubtract(mixed $date, string $interval): string

Section titled “dateSubtract(mixed $date, string $interval): string”

Subtracts time intervals from dates.

{{ "now" | dateSubtract('1 day') | dateFormat('Y-m-d') }}
{{ event.date | dateSubtract('2 weeks') | dateFormat('F j, Y') }}
{# Calculate past dates #}
{% set lastWeek = "now" | dateSubtract('7 days') %}
{% set lastMonth = "now" | dateSubtract('1 month') %}

dateDiff(mixed $date1, mixed $date2): string

Section titled “dateDiff(mixed $date1, mixed $date2): string”

Returns human-readable difference between two dates.

{{ start_date | dateDiff(end_date) }}
{# Output: 2 weeks before #}
{{ "now" | dateDiff(deadline) }}
{# Output: in 3 days #}
{# Project duration #}
<p>Project duration: {{ project.start | dateDiff(project.end) }}</p>

dateStartOf(mixed $date, string $unit = 'day'): string

Section titled “dateStartOf(mixed $date, string $unit = 'day'): string”

Returns the start of a period (day, week, month, year).

{{ "now" | dateStartOf('day') | dateFormat('Y-m-d H:i:s') }}
{# Output: 2024-06-15 00:00:00 #}
{{ "now" | dateStartOf('month') | dateFormat('Y-m-d') }}
{# Output: 2024-06-01 #}
{{ "now" | dateStartOf('year') | dateFormat('Y-m-d') }}
{# Output: 2024-01-01 #}
{# Get this week's start #}
{% set weekStart = "now" | dateStartOf('week') %}

dateEndOf(mixed $date, string $unit = 'day'): string

Section titled “dateEndOf(mixed $date, string $unit = 'day'): string”

Returns the end of a period (day, week, month, year).

{{ "now" | dateEndOf('day') | dateFormat('Y-m-d H:i:s') }}
{# Output: 2024-06-15 23:59:59 #}
{{ "now" | dateEndOf('month') | dateFormat('Y-m-d') }}
{# Output: 2024-06-30 #}
{# Get month range #}
{% set monthStart = "now" | dateStartOf('month') %}
{% set monthEnd = "now" | dateEndOf('month') %}

Checks if a date falls on a weekend.

{% if event.date | dateIsWeekend %}
<span class="badge">Weekend Event</span>
{% endif %}
{# Business hours check #}
{% if "now" | dateIsWeekend %}
<p>Our office is closed on weekends</p>
{% endif %}

Checks if a date is a weekday (Monday-Friday).

{% if meeting.date | dateIsWeekday %}
<p>Regular business hours apply</p>
{% endif %}

Checks if a date is in the past.

{% if event.date | dateIsPast %}
<span class="badge">Past Event</span>
{% else %}
<a href="/register">Register Now</a>
{% endif %}
{# Deadline checking #}
{% if task.deadline | dateIsPast %}
<div class="alert alert-danger">Overdue!</div>
{% endif %}

Checks if a date is in the future.

{% if product.launch_date | dateIsFuture %}
<span class="badge">Coming Soon</span>
{% else %}
<button>Buy Now</button>
{% endif %}

Checks if a date is today.

{% if event.date | dateIsToday %}
<div class="alert alert-info">Event is TODAY!</div>
{% endif %}
{# Birthday check #}
{% if user.birthday | dateFormat('m-d') == "now" | dateFormat('m-d') %}
<p>🎉 Happy Birthday!</p>
{% endif %}

recurringMonthDate(mixed $date, mixed $targetDate = null): string

Section titled “recurringMonthDate(mixed $date, mixed $targetDate = null): string”

Gets the recurring date for a target month. Useful for subscription billing dates - automatically handles end-of-month clamping (e.g., Jan 31st becomes Feb 28th).

{# Get this month's billing date based on subscription start #}
{{ subscription.startDate | recurringMonthDate | date('M j, Y') }}
{# Get billing date for a specific month #}
{{ subscription.startDate | recurringMonthDate('2026-03-01') | date('M j, Y') }}
{# Calculate next payment date #}
{% set nextPayment = membership.startDate | recurringMonthDate %}
<p>Your next payment is on {{ nextPayment | date('F j, Y') }}</p>
{# Show payment schedule #}
{% set months = ['2026-01-01', '2026-02-01', '2026-03-01'] %}
<ul>
{% for month in months %}
<li>{{ subscription.startDate | recurringMonthDate(month) | date('F j, Y') }}</li>
{% endfor %}
</ul>

End-of-month clamping examples:

  • January 31st → February 28th (or 29th in leap year)
  • January 31st → March 31st (no clamping needed)
  • January 30th → February 28th (clamped)
  • January 15th → February 15th (no clamping needed)

dateIsRecurringDate(mixed $date, mixed $compareDate = null): bool

Section titled “dateIsRecurringDate(mixed $date, mixed $compareDate = null): bool”

Checks if a comparison date falls on the recurring day of the original date. Useful for determining if today is a billing day for a subscription.

{# Check if today is the billing day #}
{% if subscription.startDate | dateIsRecurringDate %}
<div class="alert alert-warning">Payment due today!</div>
{% endif %}
{# Check against a specific date #}
{% if subscription.startDate | dateIsRecurringDate(checkDate) %}
<p>This is a billing day for this subscription.</p>
{% endif %}
{# Billing reminder logic #}
{% set tomorrow = "now" | dateAdd('+1 day') %}
{% if subscription.startDate | dateIsRecurringDate(tomorrow) %}
<div class="notice">Payment due tomorrow!</div>
{% endif %}
{# Process payments for today #}
{% for member in members %}
{% if member.billingDate | dateIsRecurringDate %}
<div class="payment-due">{{ member.name }} - Payment due today</div>
{% endif %}
{% endfor %}

Real-World Example - Subscription Management:

{% set subscription = cms.collection.object('subscriptions', user.id) %}
<div class="subscription-info">
<h3>{{ subscription.plan }} Plan</h3>
<p>Started: {{ subscription.startDate | date('F j, Y') }}</p>
<p>Next billing: {{ subscription.startDate | recurringMonthDate | date('F j, Y') }}</p>
{% if subscription.startDate | dateIsRecurringDate %}
<div class="alert alert-info">
Your payment is being processed today.
</div>
{% endif %}
{# Show upcoming billing dates #}
<h4>Upcoming Payments</h4>
<ul>
{% for i in 1..3 %}
{% set futureMonth = "now" | dateAdd('+' ~ i ~ ' months') %}
<li>{{ subscription.startDate | recurringMonthDate(futureMonth) | date('F j, Y') }}</li>
{% endfor %}
</ul>
</div>

The date filters support natural language strings powered by Chronos:

{# Relative dates #}
{{ "now" | dateFormat('Y-m-d H:i:s') }}
{{ "today" | dateFormat('Y-m-d') }}
{{ "tomorrow" | dateFormat('Y-m-d') }}
{{ "yesterday" | dateFormat('Y-m-d') }}
{# Relative intervals #}
{{ "+1 day" | dateFormat('Y-m-d') }}
{{ "-1 week" | dateFormat('Y-m-d') }}
{{ "+2 months" | dateFormat('Y-m-d') }}
{{ "-1 year" | dateFormat('Y-m-d') }}
{# Natural language #}
{{ "next monday" | dateFormat('l, F j') }}
{{ "last friday" | dateFormat('Y-m-d') }}
{{ "first day of this month" | dateFormat('Y-m-d') }}
{{ "last day of this month" | dateFormat('Y-m-d') }}
{{ "first day of next month" | dateFormat('Y-m-d') }}
{% set event = cms.collection.object('events', 'summer-conference') %}
<div class="event-card">
<h3>{{ event.title }}</h3>
{# Display formatted date #}
<p>📅 {{ event.date | dateFormat('l, F j, Y \\a\\t g:i A') }}</p>
{# Show relative time #}
<p>{{ event.date | dateRelative }}</p>
{# Status badges #}
{% if event.date | dateIsToday %}
<span class="badge badge-primary">Today!</span>
{% elseif event.date | dateIsPast %}
<span class="badge badge-secondary">Past Event</span>
{% elseif event.date | dateIsFuture %}
<span class="badge badge-success">Upcoming</span>
{% endif %}
{# Registration deadline #}
{% set deadline = event.date | dateSubtract('1 week') %}
{% if deadline | dateIsFuture %}
<p>Registration closes {{ deadline | dateRelative }}</p>
{% else %}
<p>Registration closed</p>
{% endif %}
</div>
{% for post in cms.collection.objects('blog') %}
<article>
<h2>{{ post.title }}</h2>
{# Publication status #}
{% if post.publish_date | dateIsFuture %}
<div class="scheduled">
⏰ Scheduled for {{ post.publish_date | dateFormat('F j, Y') }}
({{ post.publish_date | dateRelative }})
</div>
{% else %}
<time>Published {{ post.publish_date | dateRelative }}</time>
{% endif %}
{# Show content only if published #}
{% if post.publish_date | dateIsPast or post.publish_date | dateIsToday %}
<p>{{ post.excerpt }}</p>
{% endif %}
</article>
{% endfor %}
{% set tasks = cms.collection.objects('tasks') %}
{% for task in tasks %}
<div class="task {% if task.due_date | dateIsPast %}overdue{% endif %}">
<h4>{{ task.title }}</h4>
{# Due date with smart formatting #}
{% if task.due_date | dateIsToday %}
<span class="due-today">Due today!</span>
{% elseif task.due_date | dateFormat('Y-m-d') == "now" | dateAdd('+1 day') | dateFormat('Y-m-d') %}
<span class="due-tomorrow">Due tomorrow</span>
{% else %}
<span>Due {{ task.due_date | dateRelative }}</span>
{% endif %}
{# Days remaining/overdue #}
{% set daysUntil = "now" | dateDiff(task.due_date) %}
<small>{{ daysUntil }}</small>
</div>
{% endfor %}
{% set now = "now" %}
{% set openTime = now | dateStartOf('day') | dateAdd('+9 hours') %}
{% set closeTime = now | dateStartOf('day') | dateAdd('+17 hours') %}
<div class="business-hours">
<h3>Store Hours</h3>
{% if now | dateIsWeekend %}
<p class="closed">🚫 Closed on weekends</p>
{% elseif now >= openTime and now <= closeTime %}
<p class="open">✅ Open until {{ closeTime | dateFormat('g:i A') }}</p>
{% elseif now < openTime %}
<p class="closed">🚫 Opens at {{ openTime | dateFormat('g:i A') }}</p>
{% else %}
<p class="closed">🚫 Closed - Opens tomorrow at 9:00 AM</p>
{% endif %}
</div>

📚 Full Documentation: For a complete reference of all 40+ filtering operators, including detailed examples and best practices, see Collection Filtering and Sorting Guide.

{# Filter blog posts by image size and status #}
{% set posts = cms.collection.objects('blog') | filterCollection([
{
property: "image.size",
operator: "gt",
value: 1000000 {# Greater than 1MB #}
},
{
property: "status",
operator: "eq",
value: "published"
}
]) %}
{# Filter by date range #}
{% set recentPosts = cms.collection.objects('blog') | filterCollection([
{
property: "date",
operator: "gte",
value: "now -30 days" | date('Y-m-d')
}
]) %}
{# Filter with user input #}
{% set filteredProducts = cms.collection.objects('products') | filterCollection([
{
property: "price",
operator: "between",
value: [get.min_price | default(0), get.max_price | default(1000)]
},
{
property: "category",
operator: "in",
value: get.categories | default([])
}
]) %}

When filtering with an array of values, you can control whether items must match ANY value (OR logic) or ALL values (AND logic) using the logic parameter.

Returns items that match ANY of the values in the array:

{# Posts tagged with 'php' OR 'javascript' OR 'web' #}
{% set posts = cms.collection.objects('blog') | filterCollection([
{
property: "tags",
operator: "contains",
value: ["php", "javascript", "web"],
logic: "or" {# Default - can be omitted #}
}
]) %}
{# Products in category 'electronics' OR 'computers' #}
{% set products = cms.collection.objects('products') | filterCollection([
{
property: "category",
operator: "equal",
value: ["electronics", "computers"]
{# logic: "or" is the default #}
}
]) %}

Returns items that match ALL of the values in the array:

{# Posts that contain ALL tags: 'php' AND 'framework' AND 'tutorial' #}
{% set advancedPosts = cms.collection.objects('blog') | filterCollection([
{
property: "tags",
operator: "contains",
value: ["php", "framework", "tutorial"],
logic: "and"
}
]) %}
{# Events on weekends AND evening time #}
{% set weekendEvenings = cms.collection.objects('events') | filterCollection([
{
property: "day_of_week",
operator: "equal",
value: ["saturday", "sunday"],
logic: "and" {# Must be both saturday AND sunday - unlikely but demonstrates concept #}
}
]) %}
{# Products with ALL specified features #}
{% set premiumProducts = cms.collection.objects('products') | filterCollection([
{
property: "features",
operator: "contains",
value: ["waterproof", "wireless", "fast-charging"],
logic: "and" {# Must have ALL three features #}
}
]) %}

Multi-tag content filtering:

{# Find blog posts that cover ALL specified topics #}
{% set comprehensivePosts = cms.collection.objects('blog') | filterCollection([
{
property: "tags",
operator: "contains",
value: ["security", "performance", "scalability"],
logic: "and"
}
]) %}
<h2>Comprehensive Guides</h2>
<p>Posts covering security, performance, and scalability:</p>
{% for post in comprehensivePosts %}
<article>{{ post.title }}</article>
{% endfor %}

Product feature requirements:

{# Products that have ALL required features #}
{% set requiredFeatures = ["bluetooth", "waterproof", "long-battery"] %}
{% set suitableProducts = cms.collection.objects('products') | filterCollection([
{
property: "features",
operator: "contains",
value: requiredFeatures,
logic: "and"
}
]) %}
<h2>Products with ALL Required Features</h2>
{% if suitableProducts | length > 0 %}
{% for product in suitableProducts %}
<div class="product">{{ product.name }}</div>
{% endfor %}
{% else %}
<p>No products match all requirements</p>
{% endif %}

Event availability filtering:

{# Events available on ALL specified days #}
{% set availableDays = ["monday", "wednesday", "friday"] %}
{% set consistentEvents = cms.collection.objects('events') | filterCollection([
{
property: "available_days",
operator: "contains",
value: availableDays,
logic: "and"
}
]) %}
LogicBehaviorUse Case
"or" (default)Match ANY valueBroad filtering, multiple categories
"and"Match ALL valuesStrict requirements, feature completeness

Performance Note: AND logic applies filters sequentially, making it more restrictive and potentially faster for large collections since it narrows results with each filter.

{# Sort by multiple criteria #}
{% set sortedPosts = cms.collection.objects('blog') | sortCollection([
{
property: "featured",
reverse: true {# Featured first #}
},
{
property: "date",
reverse: true {# Then by date descending #}
},
{
property: "title",
natural: true {# Natural string sorting #}
}
]) %}
{# Random shuffle #}
{% set randomProducts = cms.collection.objects('products') | sortCollection([
{
property: "title",
shuffle: true
}
]) %}
{# Sort by custom property #}
{% set sortedGallery = cms.collection.objects('gallery') | sortCollection([
{
property: "order",
reverse: false
}
]) %}
{% set posts = cms.collection.objects('blog')
| filterCollection([
{property: "status", operator: "eq", value: "published"},
{property: "date", operator: "lte", value: "now" | date('Y-m-d')}
])
| sortCollection([
{property: "featured", reverse: true},
{property: "date", reverse: true}
]) %}
<div class="blog-posts">
{% for post in posts %}
<article class="post {{ post.featured | bool ? 'featured' : '' }}">
<h2>{{ post.title }}</h2>
<div class="meta">
<time>{{ post.date | date('F j, Y') }}</time>
<span>{{ post.content | readtime }} min read</span>
<span>{{ post.content | wordcount }} words</span>
</div>
<p>{{ post.excerpt | truncate(150, true) }}</p>
{% if post.tags | count > 0 %}
<div class="tags">
{% for tag in post.tags %}
<span class="tag">{{ tag | titleize }}</span>
{% endfor %}
</div>
{% endif %}
</article>
{% endfor %}
</div>
{% set theme = cms.collection.objects('theme', 'colors') %}
{% set primary = theme.primary | hexToColor %}
<style>
:root {
--primary: {{ primary | hex }};
--primary-light: {{ primary | lightness('+20') | hex }};
--primary-dark: {{ primary | lightness('-20') | hex }};
--primary-rgb: {{ primary | rgb(100, false) }};
--primary-hsl: {{ primary | hsl(100, false) }};
}
.theme-preview {
background: linear-gradient(
45deg,
{{ primary | hex }},
{{ primary | hue('+60') | hex }}
);
}
</style>

filesize(mixed $bytes, int $decimals = 1): string

Section titled “filesize(mixed $bytes, int $decimals = 1): string”

Formats byte values into human-readable file sizes using decimal units (1000 bytes = 1 KB), matching Mac Finder and browser conventions.

{# Basic usage #}
{{ 500 | filesize }}
{# Output: 500 B #}
{{ 1000 | filesize }}
{# Output: 1 KB #}
{{ 1500 | filesize }}
{# Output: 2 KB #}
{{ 1000000 | filesize }}
{# Output: 1.0 MB #}
{{ 1500000 | filesize }}
{# Output: 1.5 MB #}
{{ 1000000000 | filesize }}
{# Output: 1.0 GB #}
{# Custom decimal places (only applies to MB and larger) #}
{{ 1500000 | filesize(2) }}
{# Output: 1.50 MB #}
{{ 1500000 | filesize(0) }}
{# Output: 2 MB #}
  • Uses decimal units (1000) to match Mac Finder and browser display
  • Bytes (B) and Kilobytes (KB): Always shown as whole numbers (no decimals)
  • Megabytes (MB) and larger: Shown with decimals (default: 1 decimal place)
  • Supports units: B, KB, MB, GB, TB, PB
  • Returns “0 B” for invalid or negative input
{# Display image file size #}
{% set image = cms.data('image', 'hero-image', 'image') %}
<p>File size: {{ image.size | filesize }}</p>
{# File upload listing #}
{% for file in cms.collection.objects('depot', 'downloads').files %}
<div class="file">
<span class="name">{{ file.name }}</span>
<span class="size">{{ file.size | filesize }}</span>
</div>
{% endfor %}
{# Gallery with file info #}
{% set gallery = cms.render.gallery('portfolio') %}
{% for item in gallery %}
<figure>
<img src="{{ cms.media.galleryPath(gallery, item.name) }}" alt="{{ item.alt }}">
<figcaption>
{{ item.name }} ({{ item.size | filesize }}, {{ item.width }}x{{ item.height }})
</figcaption>
</figure>
{% endfor %}
{# Storage usage display #}
{% set totalSize = files | map(f => f.size) | sum %}
<div class="storage-info">
<p>Total storage used: {{ totalSize | filesize }}</p>
</div>

price(mixed $price, string $currency = '$', string $format = 'prepend'): string

Section titled “price(mixed $price, string $currency = '$', string $format = 'prepend'): string”

Formats price values with currency symbols.

{# Basic usage #}
{{ 19.99 | price }}
{# Output: $19.99 #}
{{ 19.99 | price('') }}
{# Output: €19.99 #}
{# Different formats #}
{{ 19.99 | price('USD', 'prepend') }}
{# Output: USD19.99 #}
{{ 19.99 | price('USD', 'append') }}
{# Output: 19.99 USD #}
{{ 19.99 | price('', 'none') }}
{# Output: 19.99 #}
  • prepend (default): Places currency before the number ($19.99)
  • append: Places currency after the number with a space (19.99 USD)
  • none: Shows only the formatted number (19.99)
{# Product listing #}
{% for product in cms.collection.objects('products') %}
<div class="product">
<h3>{{ product.name }}</h3>
<span class="price">{{ product.price | price }}</span>
{# Sale price with comparison #}
{% if product.sale_price %}
<span class="sale">{{ product.sale_price | price }}</span>
<span class="original">{{ product.price | price }}</span>
{% endif %}
</div>
{% endfor %}
{# International pricing #}
{% set currency = get.currency | default('USD') %}
{% for product in products %}
<span class="price">
{{ product.price | price(currency, 'append') }}
</span>
{% endfor %}
{# Price ranges #}
{% set minPrice = products | map(p => p.price) | min %}
{% set maxPrice = products | map(p => p.price) | max %}
<p>Prices from {{ minPrice | price }} to {{ maxPrice | price }}</p>
{# Shopping cart totals #}
{% set subtotal = cart.items | map(item => item.price * item.quantity) | sum %}
{% set tax = subtotal * 0.08 %}
{% set total = subtotal + tax %}
<div class="cart-totals">
<div>Subtotal: {{ subtotal | price }}</div>
<div>Tax: {{ tax | price }}</div>
<div class="total">Total: {{ total | price }}</div>
</div>

Remember: These filters make Twig templates more powerful and help you process data without complex PHP logic in your templates!