Pulp EngineDocument Rendering
Get started

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. Use first, last or start; 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.