renderDocument() — you can use any JavaScript you want (loops, math, formatting, conditionals). Templates are for when you need to render PDFs without a JavaScript runtime: a hosted API, a Rust/Go/Python backend, or stored templates that accept dynamic data at render time. Template-mode templates must be pure display — all computation happens in your data layer, not in the template.
How it works
.map() calls, producing a JSON document with expression markers instead of concrete values.
1. Write a template
Export a function that receivesdata and returns a <Document>:
data will be a recording proxy during compilation.
2. Compile to template JSON
3. Render with data
In Node.js
From any language
The template JSON and data JSON are plain strings. Any language that can call the Forme WASM module or a future HTTP API can render templates.Expression reference
These expression nodes are what the compiler produces. You can also hand-write template JSON if you prefer.$ref — Data lookup
Dot-path traversal into the data object. Missing paths are silently omitted.$each — Array iteration
Iterates an array and renders the template for each item. Results are flattened into the parent array.as field names the loop variable (default: $item). Inside the template, $ref paths starting with that name resolve to the current item.
$if / then / else — Conditional
Rendersthen if the condition is truthy, else otherwise. The else branch is optional.
$cond — Ternary value
Three-element array:[condition, ifTrue, ifFalse].
Comparison operators
Two-element arrays. Returntrue or false.
| Operator | Example |
|---|---|
$eq | { "$eq": [{ "$ref": "status" }, "active"] } |
$ne | { "$ne": [{ "$ref": "count" }, 0] } |
$gt | { "$gt": [{ "$ref": "total" }, 1000] } |
$lt | { "$lt": [{ "$ref": "age" }, 18] } |
$gte | { "$gte": [{ "$ref": "score" }, 90] } |
$lte | { "$lte": [{ "$ref": "price" }, 50] } |
Arithmetic operators
Two-element arrays. Return a number.| Operator | Example |
|---|---|
$add | { "$add": [{ "$ref": "subtotal" }, { "$ref": "tax" }] } |
$sub | { "$sub": [{ "$ref": "price" }, { "$ref": "discount" }] } |
$mul | { "$mul": [{ "$ref": "quantity" }, { "$ref": "unitPrice" }] } |
$div | { "$div": [{ "$ref": "total" }, { "$ref": "count" }] } |
String operators
| Operator | Input | Example |
|---|---|---|
$upper | single value | { "$upper": { "$ref": "name" } } |
$lower | single value | { "$lower": { "$ref": "email" } } |
$concat | array | { "$concat": [{ "$ref": "first" }, " ", { "$ref": "last" }] } |
$format | [value, format] | { "$format": [{ "$ref": "price" }, "0.00"] } |
$count | array value | { "$count": { "$ref": "items" } } |
$format operator formats a number. The format string determines decimal places: "0.00" gives 2 decimal places, "0.0" gives 1.
expr helpers
For operations that a property-access proxy can’t capture (comparisons, arithmetic, conditionals), use theexpr helpers in your JSX template:
Available helpers
| Helper | Description | Output |
|---|---|---|
expr.eq(a, b) | Equality | { $eq: [a, b] } |
expr.ne(a, b) | Not equal | { $ne: [a, b] } |
expr.gt(a, b) | Greater than | { $gt: [a, b] } |
expr.lt(a, b) | Less than | { $lt: [a, b] } |
expr.gte(a, b) | Greater or equal | { $gte: [a, b] } |
expr.lte(a, b) | Less or equal | { $lte: [a, b] } |
expr.add(a, b) | Addition | { $add: [a, b] } |
expr.sub(a, b) | Subtraction | { $sub: [a, b] } |
expr.mul(a, b) | Multiplication | { $mul: [a, b] } |
expr.div(a, b) | Division | { $div: [a, b] } |
expr.upper(v) | Uppercase | { $upper: v } |
expr.lower(v) | Lowercase | { $lower: v } |
expr.concat(...args) | Join strings | { $concat: [...] } |
expr.format(v, fmt) | Format number | { $format: [v, fmt] } |
expr.cond(cond, t, f) | Ternary | { $cond: [cond, t, f] } |
expr.if(cond, then, else?) | Conditional | { $if, then, else } |
expr.count(v) | Array length | { $count: v } |
Truthiness
For$if and $cond, values are truthy/falsy like JavaScript with one difference:
| Value | Truthy? |
|---|---|
null | No |
false | No |
0 | No |
"" (empty string) | No |
[] (empty array) | No |
| Everything else | Yes |