Helpers & Globals
Every template is rendered with more than just the props you pass. Two other categories are available automatically: helpers, which are functions you call to format or transform values, and globals, which are values the ssg/render layer injects on every render. This page covers both.
Built-in Helpers
Helpers are functions you call directly in your templates. ReeWeb ships with helpers from two sources:
Base Helpers (from lib/template_helpers.ts)
These are always available via props.helpers.xxx:
| Helper | Returns | Example |
|---|---|---|
url(path) | Ensures a path starts with / | <a href="{= url('profile') }"> |
localized_path(canonical) | Localised path for the active locale | <a href="{~ props.helpers.localized_path('/blog') }"> |
nav_label(key) | Translated nav label from props.nav | {= nav_label('users') } |
is_current(url) | Active nav class if current URL matches | <a class="{= is_current('/about') }"> |
key_values(obj) | Spreads an object's entries as HTML attributes | <div ...rest> (shorthand) |
js_date_to_locale_string(val) | Locale-formatted date | {= js_date_to_locale_string(post.date) } |
js_time_to_locale_string(val) | Locale-formatted time | {= js_time_to_locale_string(event.time) } |
js_datetime_to_locale_string(val) | Locale-formatted date + time | {= js_datetime_to_locale_string(record.updated_at) } |
js_timestamp_to_locale_string(val) | Locale-formatted timestamp incl. seconds | {= js_timestamp_to_locale_string(log.created_at) } |
js_date_to_iso_string(val) | ISO date string (YYYY-MM-DD) | <time datetime="{= js_date_to_iso_string(post.date) }"> |
js_datetime_to_iso_string(val) | YYYY-MM-DD HH:mm (space, not T) | <td>{= js_datetime_to_iso_string(val) }</td> |
js_timestamp_to_iso_string(val) | YYYY-MM-DD HH:mm:ss (space, not T) | sortable timestamps |
display_currency(val, locale?, hide_zero?, symbol?) | Currency string (default €) | {~ display_currency(record.price) } |
display_percent(val) | Percent string, locale-aware | {= display_percent(rate) } |
yes_no(val, type?) | Yes/No badge (HTML) | {~ yes_no(record.is_active) } |
pill(text, class) | Single-pill HTML <div> | {~ pill(status, 'pill-info') } |
tags(csv, class?) | Renders comma-separated string as pills | {~ tags(user.tags) } |
human_bytes(bytes) | Human-readable byte count | {= human_bytes(file.size) } |
urlencode(str) / urldecode(str) | URL component coding | <a href="?q={= urlencode(query) }"> |
tw_merge(...classes) | Merge Tailwind classes, last conflict wins | class="{= tw_merge('p-2', props.class) }" |
highlight(code, lang?) | Server-side syntax highlighting (hljs) | {~ highlight(snippet, 'ts') } |
webp(path, w?) / jpeg(path, w?) / avif(path, w?) | URL of one generated image variant | <img src="{= webp(src, 800) }"> |
srcset(path, format?) | srcset across the configured widths | <source srcset="{= srcset(src, 'webp') }"> |
Project-Specific Helpers (src/lib/project_helpers.ts)
src/lib/project_helpers.ts is where you add your own project-specific helpers. A fresh ReeWeb project already ships two there - md() (render a short string of inline markdown) and md_code() (render markdown through the same highlighter .md doc pages use) - exported through project_helper_functions. Add yours alongside them.
To add a helper, export it from this file and register it in the object passed to create_template_helpers. See the Custom Helpers section below for the pattern.
Calling Helpers in Templates
Helpers are available as bare names in .ree templates - no prefix needed. The template engine injects them automatically at render time via the helpers object:
<a href="{~ localized_path('/blog') }">Blog</a>
<time datetime="{= js_date_to_iso_string(post.date) }">{= js_date_to_locale_string(post.date) }</time>
{~ yes_no(record.is_active) }
A few details worth knowing:
- Date/time helpers default to
props.locale, the active BCP 47 locale ("en-us"for English,"sl-si"for Slovenian). Pass an explicit second argument to override:js_date_to_locale_string(date, "fr-fr"). display_currencytakes optional arguments:display_currency(val, locale?, hide_zero?, symbol?).hide_zero = truereturns the empty string for zero values.yes_noreturns HTML, so use the raw output tag ({~ }) when you call it....restshorthand in templates spreads an object's entries as HTML attributes viakey_values()- used in components for attribute passthrough.nav_label()readsprops.nav, notprops.translations. Markdown pages get their route strings spread ontoprops, sonav_label('home')resolves there. A.reepage only receivesprops.translations, so unless its.tssibling supplies anavobject the helper returns its__key__miss marker - use{_ nav.home }in.reetemplates instead.
Custom Helpers
To add a project-wide helper that's available during static site generation, edit src/lib/project_helpers.ts and add your function to the project_helper_functions object:
// src/lib/project_helpers.ts
export const project_helper_functions: Record<string, unknown> = {
// ... add your helpers here
uppercase: (text: string) => text.toUpperCase(),
};
Once registered via create_template_helpers(props, project_helper_functions), the helper is available in every template:
{#each props.records as user }
<tr>
<td>{= uppercase(user.name) }</td>
</tr>
{/each}
A few patterns that come up often:
- Formatting - single-purpose transforms (
price,slug). - Conditional display - return one string in one case, another in another. Keeps the template free of nested
{#if}blocks. - HTML generation - return a small chunk of markup (a badge, a status pill). Always use
{~ }to output the result.
Helper Rules
- Helpers are functions, always called with
().{= uppercase(name) }works;{= uppercase }does not. - Helpers receive only their arguments. They cannot read template variables they weren't passed.
- Helpers run during rendering. Return values are inserted into the template output.
The most common mistake is referencing a helper that isn't registered - the template throws "helper is not defined" at render time. The fix is adding the function to project_helper_functions in src/lib/project_helpers.ts or to create_default_helpers() in lib/template_helpers.ts.
Global Variables
The ssg/render layer injects a set of values into props automatically, assembled by scripts/shared/page_data.ts. You access them the same way as anything else:
| Variable | Source | Description |
|---|---|---|
props.locale | URL resolution | Active BCP 47 locale ("en-us", "sl-si") |
props.lang | URL resolution | Same value as props.locale (kept for the engine's variant lookup) |
props.html_lang | Derived | Short language subtag ("sl") for the <html lang="..."> attribute |
props.locale_url_prefix | Locale resolution | URL prefix for the active locale ("" for the default locale, "/en-us" otherwise) |
props.request_url | Current request | Relative URL of the current page |
props.canonical_path | Route resolution | The page's canonical (untranslated) path, used by localized_path_for_locale() |
props.canonical_url | SSG only | Absolute canonical URL, omitted in dev (which is never indexed) |
props.rendered_at | new Date().toISOString() | Render timestamp (ISO 8601) |
props.active_locales | $config/supported_locales | Locale codes shown in the picker (soft-launch locales filtered out) |
props.soft_launch_locales | $config/supported_locales | Locales built but excluded from the picker, sitemap, and feeds |
props.locales | $config/supported_locales | Every locale with translation files |
props.default_locale | $config/supported_locales | The locale served with no URL prefix |
props.locale_names | $config/supported_locales | Map of code → display name |
props.locale_self_names | $config/supported_locales | Map of code → that locale's own name for itself |
props.locale_urls | Route resolution | Map of code → this page's URL in that locale |
props.hreflang_links | Route resolution | { locale, href } pairs for <link rel="alternate"> tags |
props.localized_url | Derived | (path, locale) => url - resolves a canonical path to a locale's URL |
props.noindex | Derived | true when the page's locale is in soft_launch_locales |
props.site_name | .env / config | Site name |
props.site_url | SITE_URL env var | Full site URL |
props.base_url | BASE_URL env var | Base URL path the site is served from |
props.year | new Date().getFullYear() | Current year for copyright |
props.version | package.json (prod) or a dev-session token | Cache-busting token for the ?v= query param on CSS/JS tags |
props.helpers | create_template_helpers() | Object of template helper functions |
props.is_dev | Render mode | true when running bun dev |
Project hooks can add further fields via page_data_extras() - see Project Hooks - and any data your .ts sibling's load_template_data() returns is merged on top of all of the above.
These read just like any other field in templates:
<footer>© {= props.year } ReeWeb</footer>
{#if props.is_dev }
<div class="dev-banner">Development mode</div>
{/if}
Rendering to a String
In the SSG script flow, templates are rendered to HTML files via scripts/ssg.ts. For programmatic use, you can use the template engine directly:
const html = await engine.render(template_name, data);