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:

HelperReturnsExample
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 winsclass="{= 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_currency takes optional arguments: display_currency(val, locale?, hide_zero?, symbol?). hide_zero = true returns the empty string for zero values.
  • yes_no returns HTML, so use the raw output tag ({~ }) when you call it.
  • ...rest shorthand in templates spreads an object's entries as HTML attributes via key_values() - used in components for attribute passthrough.
  • nav_label() reads props.nav, not props.translations. Markdown pages get their route strings spread onto props, so nav_label('home') resolves there. A .ree page only receives props.translations, so unless its .ts sibling supplies a nav object the helper returns its __key__ miss marker - use {_ nav.home } in .ree templates 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:

VariableSourceDescription
props.localeURL resolutionActive BCP 47 locale ("en-us", "sl-si")
props.langURL resolutionSame value as props.locale (kept for the engine's variant lookup)
props.html_langDerivedShort language subtag ("sl") for the <html lang="..."> attribute
props.locale_url_prefixLocale resolutionURL prefix for the active locale ("" for the default locale, "/en-us" otherwise)
props.request_urlCurrent requestRelative URL of the current page
props.canonical_pathRoute resolutionThe page's canonical (untranslated) path, used by localized_path_for_locale()
props.canonical_urlSSG onlyAbsolute canonical URL, omitted in dev (which is never indexed)
props.rendered_atnew Date().toISOString()Render timestamp (ISO 8601)
props.active_locales$config/supported_localesLocale codes shown in the picker (soft-launch locales filtered out)
props.soft_launch_locales$config/supported_localesLocales built but excluded from the picker, sitemap, and feeds
props.locales$config/supported_localesEvery locale with translation files
props.default_locale$config/supported_localesThe locale served with no URL prefix
props.locale_names$config/supported_localesMap of code → display name
props.locale_self_names$config/supported_localesMap of code → that locale's own name for itself
props.locale_urlsRoute resolutionMap of code → this page's URL in that locale
props.hreflang_linksRoute resolution{ locale, href } pairs for <link rel="alternate"> tags
props.localized_urlDerived(path, locale) => url - resolves a canonical path to a locale's URL
props.noindexDerivedtrue when the page's locale is in soft_launch_locales
props.site_name.env / configSite name
props.site_urlSITE_URL env varFull site URL
props.base_urlBASE_URL env varBase URL path the site is served from
props.yearnew Date().getFullYear()Current year for copyright
props.versionpackage.json (prod) or a dev-session tokenCache-busting token for the ?v= query param on CSS/JS tags
props.helperscreate_template_helpers()Object of template helper functions
props.is_devRender modetrue 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);