Expression reference
The helper catalogue drives these tables, editor menus, validation and the AI prompt. Build template-model, data-adapter and html-renderer, then run the command above to regenerate. Examples use en-US, UTC and a render clock of 2026-01-02; amount is 1234.567, issued is 2026-01-01, and items contains two named rows with amounts 1.005 and 2.005.
Helpers work in HTML/PDF, DOCX/PPTX and CSV/XLSX column formats and headers. A bare helper name such as {{date}} reads the current object’s own data field; arguments invoke the helper. Handlebars built-ins if, unless, each and with also work. Data names such as first, last, index, key, root and render are reserved.
Helper eq "007" 7 is true: numeric strings compare numerically. Structured eq stays strict unless valueType opts into coercion. Arithmetic and numeric comparisons use decimals internally; outputs are JavaScript numbers, with about 15 significant digits of precision. JEXL arithmetic remains floating point.
Falsy values are false, null, missing, empty string, 0, NaN and empty arrays. Logic arguments are evaluated eagerly. Default/coalesce retain whitespace, empty objects, 0 and false; condition isEmpty also treats whitespace and empty objects as empty.
nl2br escapes text and emits breaks in HTML/PDF; plain formats retain literal newlines. Truncation, substring and length count Unicode code points. Replace is literal, including replacement text. Date addition uses UTC and clamps month ends. Date difference uses elapsed fractional days/weeks/hours/minutes; months and years compare calendar month indexes rather than completed anniversaries.
Optional arguments end in ?, variadic arguments start with … . Min/max accept either an array and field or positional numbers. Formatting options are described in the API guide.
Format
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
currency value code? |
Format currency using ISO minor units | {{currency amount "USD"}} |
$1,234.57 |
Null/missing gives blank (except bare-field fallback). |
number value decimals? |
Fixed decimal number (legacy) | {{number amount 2}} |
1234.57 |
Null/missing gives blank (except bare-field fallback). |
date value pattern? |
Date presets with legacy pattern fallback | {{date issued "short"}} |
01/01/2026 |
Null/missing gives blank (except bare-field fallback). |
formatNumber value code? currency? |
Locale-aware number format code | {{formatNumber amount "N2"}} |
1,234.57 |
Null/missing gives blank (except bare-field fallback). |
percent value decimals? |
Format a ratio as percent | {{percent ratio 1}} |
12.3% |
Null/missing gives blank (except bare-field fallback). |
compact value decimals? |
Compact number | {{compact amount}} |
1.2K |
Null/missing gives blank (except bare-field fallback). |
bytes value decimals? |
SI bytes, base 1000 | {{bytes size}} |
1.5 kB |
Null/missing gives blank (except bare-field fallback). |
ordinal value |
English ordinal suffix | {{ordinal rank}} |
21st |
Null/missing gives blank (except bare-field fallback). |
formatDate value pattern? |
Strict LDML date pattern | {{formatDate issued "dd/MM/yyyy"}} |
01/01/2026 |
Null/missing gives blank (except bare-field fallback). |
time value pattern? |
Format a time | {{time issued}} |
12:00 AM |
Null/missing gives blank (except bare-field fallback). |
relativeDate value |
Date relative to render clock or base | {{relativeDate issued}} |
yesterday |
Null/missing gives blank (except bare-field fallback). |
duration value |
Format elapsed seconds | {{duration seconds style="clock"}} |
01:01:01 |
Null gives zero duration; missing/invalid gives blank. |
uppercase value |
Uppercase text | {{uppercase name}} |
ACME LTD |
Null/missing gives blank (except bare-field fallback). |
lowercase value |
Lowercase text | {{lowercase name}} |
acme ltd |
Null/missing gives blank (except bare-field fallback). |
round value decimals? mode? |
Exact decimal rounding | {{round amount 2 "halfEven"}} |
1234.57 |
Null/invalid gives 0. |
Aggregate
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
distinctCount items field |
Count distinct non-null values | {{distinctCount lines "sku"}} |
0 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
median items field |
Median numeric value, zero when empty | {{median lines "days"}} |
0 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
sumIf items field conditionField value |
Sum values matching a condition; op defaults to eq | {{sumIf invoices "total" "status" "overdue"}} |
0 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
countIf items field value |
Count matching rows; op defaults to eq | {{countIf lines "qty" 0 op="gt"}} |
0 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
avgIf items field conditionField value |
Average values matching a condition | {{avgIf invoices "total" "status" "overdue"}} |
0 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
weightedAvg items field weightField |
Weighted average, zero when weights total zero | {{weightedAvg lines "price" "qty"}} |
0 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
runningSum items field index |
Inclusive running sum in displayed order | {{runningSum lines "amount" @index}} |
0 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
sum items field |
Exact sum of a field | {{sum items "amount"}} |
3.01 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
avg items field |
Exact average of a field | {{avg items "amount"}} |
1.505 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
min items field? ...values |
Minimum of an array field or positional values | {{min 3 1 2}} |
1 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
max items field? ...values |
Maximum of an array field or positional values | {{max 3 1 2}} |
3 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
count items field? |
Count rows, optionally with a truthy field | {{count items}} |
2 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
join items field separator? |
Join an array field | {{join items "name" ", "}} |
Alpha, Beta |
Non-array/empty gives blank; null fields skipped. |
sumRounded items field decimals mode? |
Round each row then sum to reconcile displayed totals | {{sumRounded items "amount" 2}} |
3.02 |
Non-array/empty gives 0; null/non-numeric fields skipped. |
Structural
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
pageRef anchor |
Page containing an anchor | {{pageRef "terms"}} |
pe:ref:terms |
Null/missing gives blank (except bare-field fallback). |
asset ref |
Resolve a tenant asset by id, stable name or filename | {{asset 'acme-logo'}} |
/assets/~ref/acme-logo |
Null/missing gives blank (except bare-field fallback). |
groupBy items field |
Group rows in a block | {{#groupBy items "category"}}...{{/groupBy}} |
... |
Non-array gives no groups. |
lookup object path |
Look up a field | {{lookup customer "name"}} |
Acme |
Missing object/path gives blank. |
Logic
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
eq a b |
Equal; numeric strings and ISO dates compare by value | {{eq total 100}} |
true |
Numeric/ISO pairs compare by value; otherwise strict equality or same-type string ordering. |
ne a b |
Not equal; numeric strings and ISO dates compare by value | {{ne total 100}} |
false |
Numeric/ISO pairs compare by value; otherwise strict equality or same-type string ordering. |
gt a b |
Greater than; numeric strings and ISO dates compare by value | {{gt total 100}} |
false |
Numeric/ISO pairs compare by value; otherwise strict equality or same-type string ordering. |
gte a b |
Greater than or equal; numeric strings and ISO dates compare by value | {{gte total 100}} |
true |
Numeric/ISO pairs compare by value; otherwise strict equality or same-type string ordering. |
lt a b |
Less than; numeric strings and ISO dates compare by value | {{lt total 100}} |
false |
Numeric/ISO pairs compare by value; otherwise strict equality or same-type string ordering. |
lte a b |
Less than or equal; numeric strings and ISO dates compare by value | {{lte total 100}} |
true |
Numeric/ISO pairs compare by value; otherwise strict equality or same-type string ordering. |
and ...values |
True when all values are Handlebars-truthy | {{and paid (not disputed)}} |
true |
False for any falsy argument; no arguments gives true. |
or ...values |
True when any value is Handlebars-truthy | {{or paid approved}} |
true |
True for any truthy argument; no arguments gives false. |
not value |
Negate Handlebars truthiness | {{not paid}} |
false |
Null/missing gives true. |
iif condition yes no |
Inline conditional (arguments evaluated eagerly) | {{iif (gt daysLate 0) "OVERDUE" "On time"}} |
OVERDUE |
Falsy condition selects no; arguments are eager. |
Math
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
add a b |
Add using exact decimals; invalid operands return blank | {{add 0.1 3}} |
3.1 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
subtract a b |
Subtract using exact decimals; invalid operands return blank | {{subtract 0.1 3}} |
-2.9 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
multiply a b |
Multiply using exact decimals; invalid operands return blank | {{multiply 0.1 3}} |
0.3 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
divide a b |
Divide using exact decimals; invalid operands return blank | {{divide 0.1 3}} |
0.03333333333333333 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
mod a b |
Remainder using exact decimals; invalid operands return blank | {{mod 0.1 3}} |
0.1 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
abs value |
Absolute value | {{abs amount}} |
1234.567 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
ceil value decimals? |
Round toward positive infinity | {{ceil amount 2}} |
1234.57 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
floor value decimals? |
Round toward negative infinity | {{floor amount 2}} |
1234.56 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
percentOf part whole decimals? |
Calculate a percentage, without formatting | {{percentOf 1 3 1}} |
33.3 |
Null/empty/invalid operand gives blank; zero divisor gives blank. |
String
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
default value fallback |
Fallback for null, empty string or empty array; keep 0 | {{default notes "N/A"}} |
N/A |
Null, missing, empty string/array uses fallback; retains 0 and false. |
coalesce ...values |
First nonempty value; keep 0 | {{coalesce notes reference "N/A"}} |
INV-1 |
Skips null, missing, empty string/array; retains 0 and false. |
truncate value length suffix? |
Code-point truncation; length includes suffix | {{truncate notes 20}} |
(blank) |
Null/missing gives blank (except bare-field fallback). |
replace value search replacement |
Literal replace-all, without regex | {{replace name "Ltd" "Limited"}} |
acme Limited |
Null/missing gives blank (except bare-field fallback). |
trim value |
Trim whitespace | {{trim name}} |
acme Ltd |
Null/missing gives blank (except bare-field fallback). |
urlEncode value |
Encode one URL path or query component | {{urlEncode id}} |
(blank) |
Null/missing gives blank (except bare-field fallback). |
substring value start end? |
Slice code points | {{substring name 0 3}} |
acm |
Null/missing gives blank (except bare-field fallback). |
padStart value length char? |
Pad the start | {{padStart number 6 "0"}} |
000042 |
Null/missing starts as empty text, then pads. |
padEnd value length char? |
Pad the end | {{padEnd name 10}} |
acme Ltd |
Null/missing starts as empty text, then pads. |
titlecase value |
Capitalize words | {{titlecase name}} |
Acme Ltd |
Null/missing gives blank (except bare-field fallback). |
capitalize value |
Capitalize first code point | {{capitalize name}} |
Acme Ltd |
Null/missing gives blank (except bare-field fallback). |
concat ...values |
Concatenate values | {{concat firstName " " lastName}} |
Ada Lovelace |
Null/missing gives blank (except bare-field fallback). |
nl2br value |
Escaped HTML line breaks; literal newlines in plain formats | {{nl2br notes}} |
(blank) |
Null/missing gives blank (except bare-field fallback). |
length value |
Code-point string length or array length | {{length name}} |
8 |
Null/missing gives 0. |
Array
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
at items index |
Array element, negative index counts from end | {{at items -1}} |
[object Object] |
Non-array/out-of-range gives blank. |
slice items start end? |
Array slice for subexpressions and each | {{#each (slice items 0 3)}}{{this}}{{/each}} |
[object Object][object Object] |
Non-array gives an empty array. |
includes items value |
Array membership or string substring | {{includes tags "urgent"}} |
true |
Non-array/non-string gives false. |
Date
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
dateAdd date amount unit |
Add UTC days/weeks/months/years/hours/minutes; clamp month ends | {{dateAdd issued 30 "days"}} |
2026-01-31T00:00:00.000Z |
Invalid/missing date gives blank. |
dateDiff from to unit |
Elapsed days/weeks/hours/minutes or calendar month/year difference | {{dateDiff issued @render.today "days"}} |
1 |
Invalid/missing date gives blank. |
I18n
| Signature | Description | Example | Output | Null/missing behavior |
|---|---|---|---|---|
t key |
Translate an ICU message | {{t "invoice.title"}} |
invoice.title |
Null/missing gives blank (except bare-field fallback). |
plural value |
Select a plural category | {{plural n one="item" other="items"}} |
items |
Null/missing gives blank (except bare-field fallback). |
spellNumber value |
Spell a number | {{spellNumber amount}} |
one thousand two hundred thirty-four point five hundred sixty-seven |
Null/missing gives blank (except bare-field fallback). |
amountInWords value |
Spell a currency amount | {{amountInWords amount currency="USD"}} |
One thousand two hundred thirty-four dollars and fifty-seven cents |
Null/missing gives blank (except bare-field fallback). |
Structured conditions and JEXL
| Operators | Behavior |
|---|---|
| eq, ne | Strict equality by default; no implicit coercion. |
| gt, gte, lt, lte | Numbers only by default (legacy behavior). |
| exists, notExists | Null/missing only; empty text/arrays exist. |
| contains, notContains | String substring or strict array element membership. |
| in, notIn | Scalar membership or any array intersection. |
| startsWith, endsWith | String or number prefix/suffix. |
| matches | Unicode regex; optional case-insensitive matching. |
| isEmpty, isNotEmpty | Null, missing, whitespace-only text, empty arrays/objects. 0 and false are nonempty. |
| between | Inclusive numeric range [min,max] by default. |
Set caseInsensitive:true for case-folded string comparisons and valueType:"string"|"number"|"date" for explicit coercion. Invalid coerced operands cannot compare equal or satisfy ordering. Numbers use decimals; dates compare parsed instants. Date comparison values may use @now, @today or @today-30d (d/w/m/y, plus or minus).
Patterns are capped at 200 characters; inputs at 10,000 characters. Invalid or unsafe regexes produce template-expression errors with source-node locations. Validation precedes execution; the pattern cache is bounded. Conservative safety checks can reject valid complex patterns. Lists accept up to 500 scalar values; between requires two bounds.
The expression builder serializes new operators, case options and coercion through the shared condition transform. Old equality previews retain JEXL equality: raw JEXL == can coerce types. Transforms include lower, contains, inList, startsWith, endsWith, isEmpty, matches and toTime, registered on the engine’s dedicated JEXL instance.
Report globals
Use {{@render.now}} in Handlebars or $render.now in JEXL conditions and field mappings. Root-template metadata and the same captured clock remain available in each/repeater and nested template references, separately from payload fields. No tenant or actor identity is exposed.
| Field | Meaning |
|---|---|
| now | Captured UTC ISO timestamp. |
| today | Calendar date in the effective IANA time zone. |
| templateKey, templateName, templateVersion | Root template and resolved version. |
| label | Version label when requested. |
| format | pdf, html, docx, pptx, csv or xlsx. |
| locale, timeZone | Effective language and zone; defaults en-AU and host zone. |
| requestId | Request/execution identifier, when supplied. |
| preview | True for preview/validation. |
An ISO date-time now request override makes rendering repeatable. Otherwise one server instant is captured per request/batch; item overrides beat the batch override. 2026-03-31T23:30:00Z gives today=2026-04-01 in Pacific/Auckland. Audit and usage timestamps keep actual server time.
The header/footer date token uses this clock when a locale is explicit or now is supplied; with neither, legacy behavior is retained. RelativeDate uses the render clock unless base= overrides it. Durable async jobs persist their submission clock before work and retain it with results. Existing restart policy can abandon interrupted jobs; worker resumption is not added. Template-testing defaults to 2026-01-01T00:00:00.000Z, with fixture/case overrides.
Running values and notes
running name policy="first" default="": structured page-chrome text only. Usefirst,lastorstart; see the long-document typesetting section in the API guide.footnote body: inserts an automatically numbered footnote reference in text, headings and table cells. The body can be a literal or a bound value.endnote body: inserts an endnote reference, with lower-Roman numbering by default outside Word.
Note helpers return an empty string in attribute, chart, playground and spreadsheet contexts. Their literal or resolved body is never compiled a second time. Word uses native note numbering; other document formats deduplicate identical resolved bodies within each numbering scope.