Filters and Functions
Accent CMS templates support both filters and functions to transform data and generate dynamic content.
Filters vs Functions
- Filters transform values using the pipe syntax:
{{ value | filter }} - Functions generate values:
{{ function() }}
Available Filters
date
Formats date strings using chrono format specifiers. When no format is given, the default is %Y-%m-%d.
{# Default ISO format: 2024-01-15 #}
{{ page.date | date }}
{# Custom format: January 15, 2024 #}
{{ page.date | date("%B %d, %Y") }}
{# Short format: Jan 15 #}
{{ page.date | date("%b %d") }}
{# Year only: 2024 #}
{{ page.date | date("%Y") }}
Supported input date formats:
| Format | Example |
|---|---|
| ISO 8601 | 2024-01-15 |
| US style | 01/15/2024 |
| European style | 15.01.2024 |
| Long month | January 15, 2024 |
| Short month | Jan 15, 2024 |
| ISO with time | 2024-01-15T10:30:00 |
If the date string cannot be parsed, the original value is returned unchanged.
truncate
Truncates text to a specified length with ellipsis.
{{ page.content | truncate(100) }}
Example output: This is a very long content string that will be...
The length is counted in characters, not bytes, so an accented Latin
letter, a CJK character, and an emoji each count as one no matter how many
bytes they occupy – truncate(60) on a German or Japanese title keeps 60
characters. Text already at or below the limit is returned unchanged, with
no ellipsis appended.
The cut falls at exactly that character position, so it can land inside a
word. There is no killwords argument.
slugify
Converts text to URL-friendly slugs.
{{ page.title | slugify }}
| Input | Output |
|---|---|
Hello World | hello-world |
My Blog Post! | my-blog-post |
Rust & MiniJinja | rust-minijinja |
Use cases:
- Generating anchor IDs:
<h2 id="{{ page.title | slugify }}"> - Creating CSS classes:
<article class="post-{{ page.title | slugify }}">
Available Functions
now()
Returns the current datetime as an object with date and time components.
Properties:
| Property | Description | Example |
|---|---|---|
.year | 4-digit year | 2026 |
.month | Month number (1-12) | 1 |
.day | Day of month (1-31) | 25 |
.hour | Hour (0-23) | 14 |
.minute | Minute (0-59) | 30 |
.second | Second (0-59) | 45 |
Usage Examples:
Display the current year in a footer:
<footer>
© {{ now().year }} {{ site.name }}
</footer>
Display the full date:
<p>Generated: {{ now().year }}-{{ now().month }}-{{ now().day }}</p>
Display date and time:
<time>{{ now().year }}-{{ now().month }}-{{ now().day }} {{ now().hour }}:{{ now().minute }}</time>
Common patterns:
{# Copyright notice with dynamic year #}
<footer>© 2025-{{ now().year }} My Company</footer>
{# Build timestamp #}
<meta name="build-date" content="{{ now().year }}-{{ now().month }}-{{ now().day }}">
{# Conditional content based on time #}
{% if now().hour >= 6 and now().hour < 12 %}
Good morning!
{% elif now().hour >= 12 and now().hour < 18 %}
Good afternoon!
{% else %}
Good evening!
{% endif %}
media(path, page_url?)
Generates URLs for serving media files . Supports two media locations: a shared site-wide library and page-local files co-located with content.
Arguments:
| Argument | Required | Description |
|---|---|---|
path | Yes | File path. Absolute (starts with /) for shared media, relative for page-local |
page_url | No | Current page URL for resolving page-local paths. Use page.url |
Usage Examples:
Shared media (site-wide assets in site/media/):
{# Absolute path -> /media/logos/logo.svg #}
<img src="{{ media('/logos/logo.svg') }}" alt="Logo">
{# Shared PDF #}
<a href="{{ media('/docs/whitepaper.pdf') }}">Download PDF</a>
Page-local media (files alongside content markdown):
{# Relative path + page.url -> /content-media/blog/my-post/hero.jpg #}
<img src="{{ media('hero.jpg', page.url) }}" alt="{{ page.title }}">
{# Gallery image next to the page #}
<img src="{{ media('gallery/photo1.jpg', page.url) }}">
Using with custom frontmatter fields:
--- title: My Post hero_image: hero.jpg ---
{% if page.custom.hero_image is defined %}
<img src="{{ media(page.custom.hero_image, page.url) }}" alt="{{ page.title }}">
{% endif %}
The media() function returns a safe string that is not HTML-escaped, so it can be used directly in src and href attributes without the | safe filter. On a sub-path deployment the returned URL carries site.base_path automatically (passing the already-prefixed page.url is fine), so media links keep working without changes.
responsive_image(path, alt, sizes?)
Generates a responsive <img> tag with a srcset at each configured breakpoint , so the browser downloads the smallest image that fills the layout slot. It builds on the image processing pipeline, which serves each width on demand.
Arguments:
| Argument | Required | Description |
|---|---|---|
path | Yes | Media URL or path. Combine with media() for page-local images |
alt | Yes | Alt text for the image |
sizes | No | The sizes attribute hint (defaults to 100vw) |
Usage Examples:
Shared media:
{{ responsive_image('/photos/hero.jpg', 'Mountain at sunrise') }}
Page-local media (resolve the path through media() first):
{{ responsive_image(media('hero.jpg', page.url), page.title, '(max-width: 768px) 100vw, 50vw') }}
From a custom frontmatter field:
--- title: My Post responsive_hero: hero.jpg ---
{% if page.custom.responsive_hero is defined %}
{{ responsive_image(media(page.custom.responsive_hero, page.url), page.title, '(max-width: 768px) 100vw, 50vw') }}
{% endif %}
The generated tag includes loading="lazy" and a src at the middle breakpoint as a fallback for browsers that ignore srcset. The candidate widths come from the media processing configuration. Returns a safe HTML string.
video_embed(url, width?, height?)
Turns a YouTube or Vimeo URL into a privacy-respecting <iframe> embed . YouTube URLs become youtube-nocookie.com embeds and Vimeo URLs get dnt=1 (do-not-track), so neither platform sets tracking cookies until the visitor plays the video. Unrecognized URLs are embedded as-is, which is useful for direct embed URLs.
For self-hosted video files stored alongside your content, use video() instead.
Arguments:
| Argument | Required | Description |
|---|---|---|
url | Yes | A YouTube, Vimeo, or direct embed URL |
width | No | iframe width attribute, passed as a keyword (default 560) |
height | No | iframe height attribute, passed as a keyword (default 315) |
Usage Examples:
{# YouTube -> youtube-nocookie.com embed #}
{{ video_embed('https://youtube.com/watch?v=dQw4w9WgXcQ') }}
{# Vimeo with a custom size (width/height are keyword arguments) #}
{{ video_embed('https://vimeo.com/123456789', width='800', height='450') }}
From a custom frontmatter field:
--- title: My Post video_embed: https://youtube.com/watch?v=dQw4w9WgXcQ ---
{% if page.custom.video_embed is defined %}
{{ video_embed(page.custom.video_embed, width='800', height='450') }}
{% endif %}
Returns a safe HTML string. The default theme demonstrates this with a .video-embed figure that keeps the iframe in a responsive 16:9 box regardless of the width/height attributes.
recent_documents(limit, model?)
Returns the most-recently-updated pages across the entire site as a list, newest first . Use it to build a “Recently updated” sidebar, a footer block, or a homepage feed.
Arguments:
| Argument | Required | Description |
|---|---|---|
limit | Yes | Maximum number of pages to return |
model | No | Restrict the list to a single document model (e.g. "news") |
Sort key: each page is ordered by its frontmatter date when present, falling back to the file’s last-modified date (modified_date) when no date is set. Pages with neither are omitted. Dates are compared as ISO YYYY-MM-DD strings, so an explicit date always takes precedence over file modification time – which matters because operations like git clone, git checkout, or a full rebuild reset every file’s modification time at once.
Each returned entry is a page listing object exposing the usual fields: title, url, date, modified_date, lead, author, tags, and model.
Usage:
{# Last 5 updated pages, any type #}
<aside class="recent-docs">
<h4>Recently updated</h4>
<ul>
{% for p in recent_documents(5) %}
<li>
<a href="{{ p.url }}">{{ p.title }}</a>
<time datetime="{{ p.date or p.modified_date }}">{{ p.date or p.modified_date }}</time>
</li>
{% endfor %}
</ul>
</aside>
Filter to a single document model:
{# Last 5 updated "news" pages only #}
{% for p in recent_documents(5, "news") %}
<a href="{{ p.url }}">{{ p.title }}</a>
{% endfor %}
The function reads the in-memory page index, so it works identically in accent serve and accent build, and renders nothing when no pages have a usable date. The default theme ships a ready-made partials/recent-documents.html.jinja that wraps this function – include it with {% include "partials/recent-documents.html.jinja" %}.
Looking for the most-viewed pages rather than the most recently updated? That requires per-request view tracking and is a separate, server-only capability;
recent_documents()ranks by update time only.
json_ld()
Returns pre-computed JSON-LD structured data HTML for the current page . The output is a safe HTML string containing one or more <script type="application/ld+json"> blocks, or an empty string when structured data is disabled.
Usage:
{# In <head> - recommended approach #}
<head>
{{ json_ld() }}
</head>
The function reads the structured data that Accent CMS generates automatically based on page type (BlogPosting, TechArticle, WebPage, etc.). No arguments are needed.
The legacy syntax {% if page.json_ld %}{{ page.json_ld | safe }}{% endif %} also works via page.json_ld.
url(path)
Joins a root-absolute path with the site’s deployment path prefix (site.base_path) . Available as both a function and a filter. This is the helper that keeps a theme portable when the site is deployed under a sub-path, such as a GitHub Pages project site served from https://user.github.io/repo/.
Usage:
<a href="{{ url('/') }}">Home</a>
<a href="{{ url('/tags/' ~ tag.name) }}">{{ tag.name }}</a>
<link rel="stylesheet" href="{{ '/theme/assets/css/main.css' | url }}">
With site.base_path set to /repo, url('/tags') renders /repo/tags; with no base path configured it is the identity function, so the same theme works for root and sub-path deployments.
- Context URLs (
page.url, collection entries,crumb.url, media URLs) already carry the prefix, so they need no wrapping – and the join is idempotent, sourl(page.url)is safe. - Already-absolute (
http://,https://), protocol-relative (//host/...), relative paths, and#anchorspass through unchanged. - Returns a safe string, so it can be used directly in
href/src.
Wrap every hardcoded root-absolute href/src/action in your templates with url(). When a base path is set, accent build warns about hardcoded root-absolute URLs it finds in theme templates and fails the build if any unprefixed internal URL reaches the output. See Theme Portability for the full rule and Static Build Deployment for the GitHub Pages recipe.
absolute_url(url)
Makes a URL absolute by prepending site.url . Available as both a function and a filter, and used by the bundled head-meta partial for canonical links, og:url, and og:image.
Usage:
{{ absolute_url(page.url) }} {# https://example.com/blog/post/ #}
{{ page.url | absolute_url }} {# filter form, same result #}
- Root-relative paths gain the site origin (a trailing slash on
site.urlis trimmed). - Already-absolute URLs (
http://,https://) pass through unchanged. - When
site.urlis empty the input is returned unchanged. - Under a sub-path deployment the result carries
site.base_pathexactly once, whether the input is a raw path or an already-prefixed context URL –absolute_url('/feed.xml')on a site athttps://user.github.io/reporendershttps://user.github.io/repo/feed.xml.
Unlike a request-based canonical, absolute_url() does not depend on the request, so it produces identical output in accent serve and static accent build. See Social Sharing and SEO Metadata for the full head-meta system.
cdn_url(path)
Rewrites an asset path to your configured CDN origin . Use it for stylesheets, scripts, images, and any other static asset you want served from the CDN instead of the application origin.
Usage:
<link rel="stylesheet" href="{{ cdn_url('/assets/css/main.css') }}">
<script src="{{ cdn_url('/assets/js/app.js') }}"></script>
<img src="{{ cdn_url('/media/hero.webp') }}" alt="Hero">
When CDN integration is enabled, cdn_url('/assets/css/main.css') returns https://cdn.example.com/assets/css/main.css (using your configured cdn.base_url). When CDN is disabled, it returns the path joined with the site’s deployment path prefix (site.base_path, empty for root deployments) – the same behavior as url() – so the same templates work whether or not a CDN is configured, on root and sub-path deployments alike.
- Root-relative and relative paths gain the CDN origin (a trailing slash on
base_urlis trimmed). - Already-absolute (
http://,https://) and protocol-relative (//host/...) URLs pass through unchanged. - Produces identical output in
accent serveand staticaccent build, so generated HTML already points at the CDN. - Returns a safe string, so it can be used directly in
href/srcwithout| safe.
CDN integration is a Pro feature, and enabling it for accent serve --production requires a Pro license. The cdn_url() helper itself is always available (as the identity passthrough above) so themes stay portable across editions. Markdown image URLs are rewritten automatically when CDN is enabled — you only need cdn_url() for assets referenced in templates. See CDN Integration for the full configuration.
font_css_url(family)
Generates a stylesheet URL for a web font family . With the CDN font proxy enabled, fonts are fetched server-side and served from your own origin under /_fonts/, so visitor browsers never contact Google directly — eliminating the IP-address leak that European courts have ruled a GDPR violation.
Usage:
<link rel="stylesheet" href="{{ font_css_url('Inter:wght@400;700') }}">
- With the font proxy enabled, this renders a same-origin path like
/_fonts/css/inter-wght-400-700.cssthat works identically inserveand staticbuild. - With the proxy disabled, it falls back to a direct
https://fonts.googleapis.com/css2?...URL, so the helper is safe to use on any edition. - List the families you want proxied under
cdn.font_proxy.familiesso static builds can pre-download them.
llms_discovery_link()
Emit the <link rel="alternate" type="text/plain" href="/llms.txt"> tag
that points LLM crawlers (ChatGPT, Perplexity, Gemini, and others) at the
site’s machine-readable overview .
Usage:
<head>
{{ llms_discovery_link() }}
</head>
When llms.enabled is true, the template engine also auto-injects the
same tag just before </head> on every rendered HTML page in both
accent serve and accent build. Calling this function explicitly is
therefore optional – the engine detects the existing href="/llms.txt"
and skips its automatic placement to avoid duplicates. Use the explicit
form when the theme wants control over the precise position of the tag in
the head (for example, to keep all alternate-link declarations grouped
together).
The function takes no arguments and returns a safe HTML string.
debug(value)
Outputs the debug representation of any template value. Useful for troubleshooting and understanding the structure of template variables.
Usage:
{{ debug(page) }}
{{ debug(page.tags) }}
{{ debug(site) }}
Output example:
{
"title": "Hello World",
"url": "/docs/hello-world",
"date": "2026-01-25",
"tags": [
"rust",
"web"
]
}
Debugging Tips:
Use debug() to:
- Inspect the structure of
page,site, orthemeobjects - Verify frontmatter values are parsed correctly
- Check if a variable exists before using it
- Understand what data is available in your template
Conditional debugging with dev.debug:
Use the built-in dev.debug flag to conditionally show debug output:
{% if dev.debug %}
<pre class="debug-panel">
{{ debug(page) }}
</pre>
{% endif %}
Enable debug mode in config.yaml:
dev: debug: true # Enable template debug output
Quick debug with ?acdbg query parameter:
For quick debugging without modifying templates, add ?acdbg to any page URL:
https://localhost:4400/docs?acdbg
https://localhost:4400/docs?acdbg=right
https://localhost:4400/docs?acdbg=float
This displays a panel showing all template context values (site, page, version, custom frontmatter, theme, pages, all_pages, dev, taxonomy, config, search, cache) with syntax highlighting. The panel supports three position modes (bottom, right, float) and can be resized by dragging the edge handle. Position preferences persist in localStorage. The panel only appears in dev mode and is hidden in production.
Development Context (dev)
The dev object exposes development settings to templates:
| Property | Type | Description |
|---|---|---|
dev.debug | bool | Whether debug mode is enabled |
dev.hot_reload | bool | Whether hot reload is enabled |
dev.browser_reload | bool | Whether browser auto-reload is enabled |
Usage examples:
{# Show debug panel only in debug mode #}
{% if dev.debug %}
<div class="debug-panel">
<h4>Debug Info</h4>
<pre>{{ debug(page) }}</pre>
</div>
{% endif %}
{# Show development indicators #}
{% if dev.hot_reload %}
<div class="dev-indicator">Hot reload active</div>
{% endif %}
Build Information Context
The accent object exposes version and build information:
| Property | Type | Description |
|---|---|---|
accent.version | string | Semantic version (e.g., 0.6.0) |
accent.git_hash | string | Short git commit hash (e.g., abc1234) |
accent.build_time | string | UTC build timestamp |
accent.version_string | string | Version with hash (e.g., 0.6.0 (abc1234)) |
Usage example:
<footer>
Powered by Accent CMS {{ accent.version_string }}
</footer>