Revela

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 (//), for loops and the functions on this page instead. Lumina only uses string.contains, and revela.website's own overrides add string.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:

  1. Gallery-local: {current gallery path}/{path}
  2. Shared images: _images/{path}
  3. 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') }}">&times;</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 (&amp;, &lt;, &gt;, &quot;, &#39;). Everything else, including umlauts and other non-ASCII characters, stays literal (Menü, not Men&#252;); 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