Template Functions
Revela themes are Scriban templates (.revela files). Every template has access to the built-in functions below for URLs, images, date/EXIF formatting, UI text, and Markdown. For where these are used, see Theme Authoring.
Prefer plain language features over Scriban's built-in libraries. The Standalone edition is compiled with Native AOT, which keeps only the Scriban built-ins that Revela itself references; a template calling another one (for example
array.last,array.join,math.floor) can fail there. Use indexing (list[0],list[-1]), integer division (//),forloops and the functions on this page instead. Lumina only usesstring.contains, and revela.website's own overrides addstring.replace.
Overview
🖼️ Images
find_image, variant_url: resolve images and build variant URLs.
🔗 URLs
page_url, absolute_url, asset_url: generate page, absolute, and asset links.
📅 Formatting
format_date, format_filesize, format_exif_*: human-readable values.
🌍 Text
t, html_escape: theme UI text in the site language, safe escaping.
📝 Content
markdown: render Markdown to HTML.
Image Functions
find_image
Resolve an image from the project by path. Uses the same 3-step lookup as Markdown content images.
{{~ logo = find_image "logo.jpg" ~}}
{{~ if logo ~}}
<img src="{{ variant_url(logo, 640, 'jpg') }}"
width="{{ logo.width }}" height="{{ logo.height }}">
{{~ end ~}}
| Parameter | Type | Description |
|---|---|---|
path |
string | Image path (relative to gallery, _images/, or exact) |
Returns the same image object that images and gallery.cover_image contain (or null if not found). Resolution order:
- Gallery-local:
{current gallery path}/{path} - Shared images:
_images/{path} - Exact match:
{path}as-is
The image object exposes slug, file_name, source_path, width, height, sizes, file_size, date_taken, exif, title, description, keywords, rating, color, and gamut.
color is the photo's average color as lowercase sRGB hex (#rrggbb), computed during scan, or null when unknown. Themes paint it while the photo loads; Lumina writes it as a CSS custom property:
<article style="--width:{{ image.width }}; --height:{{ image.height }};{{ if image.color }} --image-color:{{ html_escape image.color }};{{ end }}">
gamut is the color space of the photo's variants: "p3" when they are Display P3 with the profile embedded (photos with colors beyond sRGB, see Wide Gamut), otherwise "srgb". <picture> markup needs no change for P3 — browsers read the embedded profile. P3 photos also have one untagged sRGB JPEG for social previews, at the largest size up to 1920 px; use the format 'srgb.jpg' for it (see absolute_variant_url).
The descriptive fields come from the photo's XMP metadata, as Capture One and Lightroom export it:
| Field | Type | Description |
|---|---|---|
title |
string or null | XMP title; falls back to the Windows title (EXIF XPTitle) |
description |
string or null | XMP description/caption; falls back to EXIF ImageDescription |
keywords |
string[] | XMP keywords; empty when there are none |
rating |
int or null | XMP star rating: 1–5, 0 = unrated, -1 = rejected |
These are user data: escape them ({{ html_escape image.title }}) and never pass them
through t. Keywords are often private workflow markers such as Selected, so Lumina
does not show them; render them in your own theme only when that is intended:
{{~ if image.rating && image.rating >= 4 ~}}<span>★ {{ image.rating }}</span>{{~ end ~}}
{{~ for keyword in image.keywords ~}}<span>{{ html_escape keyword }}</span>{{~ end ~}}
variant_url
Build a URL for a specific image variant (size + format), resolved against the current page's asset base path.
{{ variant_url(image, 1920, "webp") }}
| Parameter | Type | Description |
|---|---|---|
image |
Image | Image object (e.g. from images or find_image) |
size |
int | Target width |
format |
string | "avif", "webp", or "jpg"; "srgb.jpg" for the social copy of a P3 photo |
Returns {assets_basepath}{image.slug}/{size}.{format}, e.g. ../images/events/fireworks/029081/1920.webp.
Choose a size from image.sizes and a format from image_formats; the helper
does not generate missing variants. Configured asset prefixes must end with /.
absolute_variant_url
Generate an absolute image-variant URL for Open Graph or other image metadata in a full page template. It respects the current gallery/photo page, deployment prefix and asset base path, including absolute CDN addresses.
{{ og_size = image.sizes[-1] }}
<meta property="og:image" content="{{ html_escape (absolute_variant_url image og_size 'jpg') }}">
image.sizes is in ascending order, so image.sizes[-1] is the largest generated size.
Social crawlers prefer JPEG around 1200–1920 px; Lumina's Partials/OpenGraphImage.revela
picks the largest generated size up to 1920 (with image_size image 1920 from
Partials/ImageHelpers.revela) and also emits og:image:width/og:image:height.
Social networks often drop color profiles, so for a photo with image.gamut == 'p3' Lumina
links the untagged sRGB copy instead — it exists at exactly that size:
{{~ og_size = image_size image 1920 ~}}
{{~ og_format = image.gamut == 'p3' ? 'srgb.jpg' : 'jpg' ~}}
<meta property="og:image" content="{{ html_escape (absolute_variant_url image og_size og_format) }}">
Without base_url, local variants use a root-relative URL; an absolute CDN URL
remains absolute. This helper needs the normal page context (base_url,
basepath, assets_basepath, and gallery or photo/image). Separately rendered
content-image partials only have the asset context: use variant_url there.
absolute_url(image) still links to the canonical photo page, not its image file.
URL Functions
page_url
Generate a URL for a page. Polymorphic — accepts an Image, Gallery, NavigationItem, or a slug string. Returns null for a navigation item that has no page (a section header), so templates can branch on it.
{{ page_url(gallery) }}
{{ page_url("gallery/vacation") }}
Returns a site-relative URL, e.g. /gallery/vacation/ (relative to the current page's base path).
absolute_url
Generate an absolute URL (including host from project.baseUrl and the deployment prefix) for Open Graph, RSS, sitemap, or JSON-LD. Same polymorphism as page_url. When baseUrl is unset it returns the root-relative form instead of failing. Escape helper results with html_escape when writing HTML attributes.
{{ absolute_url(gallery) }}
Returns e.g. https://example.com/gallery/vacation/ (or /gallery/vacation/ without baseUrl).
asset_url
Generate a URL for a theme asset (CSS, JS).
{{ asset_url "css/style.css" }}
Returns the asset path relative to the current page, including the site base path, with a content fingerprint: _assets/css/style.css?v=3f2a9c1d on the home page, ../_assets/css/style.css?v=3f2a9c1d one level down, or /gallery/_assets/css/style.css?v=3f2a9c1d with an absolute base path of /gallery/.
The v value is the start of the SHA-256 hash (8 hex characters) of exactly the file written to _assets/, after your local overrides in themes/<Theme>/Assets/ are applied. When the file changes, the URL changes, so browsers load the new file at once and the server can cache _assets/ for a long time (see Caching). A path that is not a theme asset gets the plain URL without ?v=. Always link theme assets through asset_url instead of writing _assets/ paths yourself.
Formatting Functions
format_date
Format a date with a custom format string. Month and day names follow language in site.json ("de" turns MMMM into März); an unknown language falls back to neutral (English) names.
{{ format_date image.date_taken "yyyy-MM-dd" }}
{{ format_date image.date_taken "MMMM yyyy" }}
format_filesize
Format a byte count as a human-readable string, using the number format of the site language.
{{ format_filesize image.file_size }}
Returns e.g. "2.4 MB", "340 KB" ("2,4 MB" for "language": "de").
format_exif_exposure
Format an exposure time as photography notation.
{{ format_exif_exposure image.exif.exposure_time }}
Returns e.g. "1/500s", "0.5s", "10s" (or "N/A" when unset).
format_exif_aperture
Format an aperture (f-number) as photography notation.
{{ format_exif_aperture image.exif.f_number }}
Returns e.g. "f/2.8", "f/11" (or "N/A" when unset).
Text Functions
t
Return a theme UI string (button labels, headings, screen-reader text) in the site language.
Strings come from the theme's Locales/<language>.json files; see
Theme Language.
<button aria-label="{{ html_escape (t 'photo.close') }}">×</button>
<a aria-label="{{ html_escape (t 'photo.return_to' gallery.title) }}">…</a>
| Parameter | Type | Description |
|---|---|---|
key |
string | Namespaced key, e.g. photo.close, statistics.overview |
args… |
any | Optional values for the placeholders {0}, {1}, … |
Returns plain text. The value of language in site.json is tried exactly
(de-CH), then without region (de), then en; a key missing everywhere
renders as the key itself so the gap is visible. Placeholder values are inserted
culture-invariantly.
The result is not trusted HTML: arguments are often user data such as gallery
titles. Always escape it like any other text, e.g. html_escape (t "key" value).
html_escape
Escape text for HTML content and quoted attribute values. Scriban does not escape
automatically, so wrap every dynamic value (titles, EXIF data, t results, URLs).
<img alt="{{ html_escape image.title }}" src="{{ html_escape (variant_url image 640 'jpg') }}">
Returns the text with only &, <, >, " and ' replaced (&, <, >,
", '). Everything else, including umlauts and other non-ASCII characters, stays
literal (Menü, not Menü); generated pages are UTF-8.
Content Functions
markdown
Convert Markdown text to HTML. Works as a function or a filter.
{{ "**bold** text" | markdown }}
Returns <p><strong>bold</strong> text</p>.
Template Variables
These values (not functions) are available in templates:
| Variable | Type | Description |
|---|---|---|
site |
object | Site settings from site.json |
gallery |
Gallery | Current page: title, description, body (rendered Markdown body), cover_image, template, slug, images |
images |
Image[] | Images for the current gallery |
nav_items |
NavItem[] | Navigation tree with active state |
basepath |
string | Path to the site root: relative ("", "../"), or your absolute basePath; always root-absolute on the 404 page |
assets_basepath |
string | Path/URL to image variants |
base_url |
string? | Absolute site host from project.baseUrl (null when unset) |
og_locale |
string | Open Graph locale from language in site.json ("de" → de_DE; empty when unknown) |
not_found |
bool | true only while rendering 404.html (Body/NotFound.revela) |
image_formats |
string[] | Active formats (["avif", "webp", "jpg"]) |
stylesheets |
string[] | CSS filenames resolved for the current template, plugin, and feature scopes |
scripts |
string[] | JS filenames resolved for the current template, plugin, and feature scopes |
revela |
object | Build info: revela.version, revela.kind, revela.framework, revela.configuration |
stylesheets and scripts are already filtered before the template is rendered. Templates
should emit the provided filenames in order; they do not need to inspect page scopes or asset
configuration themselves. Canonical photo pages receive only the scoped image zoom script;
Lightbox pages receive both dialog behavior and image zoom scripts.
The home page is a gallery like any other: without a title in its _index.revela, gallery.title
is the site title from site.json (then the project folder name).
Data Sources
A page can bring extra variables into its template with data in the front matter. Each key
becomes a template variable:
+++
template = "statistics/overview"
data = { galleries: "$galleries", statistics: "statistics.json" }
+++
| Source | Value |
|---|---|
$galleries |
All galleries of the site (same objects as gallery) |
$images |
The images of the current page (same objects as images) |
name.json |
Plugin-generated data from the page's folder in a plugin's folder below .revela/ (for example .revela/statistics/) (for example statistics.json, calendar.json) |
Theme extensions such as Lumina.Statistics and Lumina.Calendar provide default data sources for
their templates, so their pages work without a data line.
See Also
- Theme Authoring: where templates and the required
ContentImage.revelapartial live - Source Structure: how galleries and images map into templates