6. Data binding and expressions

A binding is a placeholder in double braces, {{ … }}, that is replaced with data when the PDF is rendered. This chapter covers everything you can write inside one: field paths, preview text, calculations, conditions and formatting.

Note: All examples in this chapter use the sample shipment examples-data.json. The text after → is exactly what the example prints, and every one is verified automatically against the engine.

Where you can use expressions

Where What you type Example
Text elements, table cells, headers and footers, group headers, barcode values, image sources, links, PDF title Template text: normal text with {{ … }} placeholders Ship to: {{ shipTo.name }}
Visible if, Checked if, a table's or repeater's Data, Sort by, Group by, One copy per A bare expression, without braces hazmat

Your first bindings

Your system sends data as JSON. Here is part of the sample shipment:

{
  "orderNumber": "SO-104412",
  "shipTo": { "name": "Northwind Retail #1187", "city": "Columbus", "state": "OH", "zip": "43215" },
  "items": [ { "sku": "APP-TS-1001", "qty": 24, "weight": 0.4 }, "…" ]
}

To print a value, write its path. A path is the field names joined with dots:

{{ orderNumber }}  →  SO-104412
{{ shipTo.name }}  →  Northwind Retail #1187
{{ shipTo.city }}, {{ shipTo.state }} {{ shipTo.zip }}  →  Columbus, OH 43215

Spaces inside the braces are optional: {{shipTo.city}} and {{ shipTo.city }} mean the same thing.

Tip: Don't type paths you can drag. The Fields tab creates the correct binding for you (chapter 3).

Suggestions while you type

When you type a name inside {{ … }} (or anywhere in a bare expression box such as Visible if), a list of suggestions opens under the box:

Use Up and Down to choose, Enter or Tab to insert, and Esc to close the list. Ctrl+Space opens it even before you type.

Preview text: {{ field | preview text }}

The designer never has real data. To make the design readable, write sample text after a |:

{{ customer.name | ACME Corp }}  →  Northwind Retail

Tip: Make preview text realistic in length. The longest name your customers have ("Northwind Retail Distribution Center #1187") shows you whether the box is wide enough. Preview PDF then shows realistic line breaks.

Everything after the first | is preview text, with its surrounding spaces trimmed. Two exceptions: a | inside quotes, and || (the "or" operator), don't count.

Paths in detail

{{ items[1].sku }}  →  ELE-PB-2000
{{ shipTo['dock door'] }}  →  Door 3
{{ $.orderNumber }}  →  SO-104412
{{ poNumbers[0] }}  →  PO-780100
Syntax Meaning
a.b.c Field c inside b inside a
list[0] First item of a list (counting starts at 0)
a['field name'] A field whose name has spaces, dashes or other special characters, e.g. shipTo['zip-code']
$ The top of the data (useful inside tables, where item is the current row)

A path that doesn't exist is simply empty. It never causes an error.

Special variables

Variable Value Where
page The current page number Anywhere
pages The total number of pages Anywhere
now The date and time of the render Anywhere
item The current row's data Table cells, repeater elements, Sort by, Group by
index The current row number, counting from 0 Table cells, repeater elements
rows All rows of the table (in footers) or of the group (in group headers and footers) Table footers, group headers and footers
group The current group: group.key, group.index (counting from 0) and group.count (rows in the group) Group headers and footers
record, recordIndex In a batch, the current record and its number (from 0) Anywhere, with One copy per set
Page {{ page }} of {{ pages }}  →  Page 2 of 3
Line {{ index + 1 }}: {{ item.sku }}  →  Line 1: APP-TS-1001
{{ group.key }} ({{ group.count }} lines)  →  Apparel (2 lines)

Operators

Operator Meaning Example Result
+ - * / % Arithmetic (% = remainder) cartons * 2 4
+ with text Joins text 'Door ' + 3 Door 3
== != Equal / not equal ('10' == 10 is true) status == 'Shipped' true
=== !== Strictly equal / not equal (same type) cartons === 2 true
< <= > >= Comparisons totalWeight > 1000 true
&& || ! And, or, not hazmat && cartons > 1 true
?? Use the right side when the left is missing (null/undefined) notes ?? 'None' None
a ? b : c If a then b, otherwise c hazmat ? 'HAZMAT' : '' HAZMAT
( ) Grouping (1 + 2) * 3 9
cartons * 2  →  4
'Door ' + 3  →  Door 3
status == 'Shipped'  →  true
cartons === 2  →  true
totalWeight > 1000  →  true
hazmat && cartons > 1  →  true
notes ?? 'None'  →  None
hazmat ? 'HAZMAT' : ''  →  HAZMAT
(1 + 2) * 3  →  9

Text values go in single or double quotes: 'Prepaid' or "Prepaid".

Warning: Calculations with decimals can show tiny rounding errors, as on any computer: 24 * 4.85 gives 116.39999999999999. Always format money and weights with formatNumber or formatCurrency:

{{ items[0].qty * items[0].unitCost }}  →  116.39999999999999
{{ formatCurrency(items[0].qty * items[0].unitCost, 'USD') }}  →  $116.40

Functions

Functions are written name(argument, argument, …). The function reference lists them all; every expression box has a Functions link under it that opens the reference in Help. A function name the designer doesn't know is flagged under the box as you type, with a suggestion when it's close to a real one: Unknown function 'formatCurency' (did you mean formatCurrency?). The most useful functions are below.

Totals and counts over a list

sum, avg, min and max take a list and the name of the field to add up. count counts items.

{{ count(items) }}  →  3
{{ sum(items, 'qty') }}  →  82
{{ max(items, 'qty') }}  →  48
{{ min(items, 'qty') }}  →  10
{{ formatNumber(sum(items, 'weight'), 2) }} lb  →  1.63 lb
{{ formatCurrency(avg(items, 'unitCost'), 'USD') }}  →  $9.67

Inside a table footer, use rows instead of the list name, e.g. {{ sum(rows, 'qty') }}. See Tables.

Numbers

{{ formatNumber(totalWeight, 1) }}  →  2,942.5
{{ formatNumber(totalWeight, 0) }}  →  2,942
{{ round(totalWeight, 2) }}  →  2942.46
{{ floor(totalWeight) }}  →  2942
{{ ceil(totalWeight) }}  →  2943
{{ formatCurrency(declaredValue, 'USD') }}  →  $1,250.00

formatNumber(value, decimals) adds thousands separators and fixes the number of decimals. formatCurrency(value, code) takes a three-letter currency code such as USD, EUR, GBP, MXN, CNY, JPY or KRW. Both follow the Document language (see below).

Dates and times

formatDate(value, pattern) accepts dates like "2026-10-18", date-times like "2026-10-20T15:45:00", and timestamps.

{{ formatDate(orderDate, 'MM/DD/YYYY') }}  →  10/18/2026
{{ formatDate(orderDate, 'DD.MM.YY') }}  →  18.10.26
{{ formatDate(orderDate, 'MMMM D, YYYY') }}  →  October 18, 2026
{{ formatDate(orderDate, 'ddd MMM D') }}  →  Sun Oct 18
{{ formatDate(shipDate, 'MM/DD/YYYY h:mm A') }}  →  10/20/2026 3:45 PM
{{ formatDate(shipDate, 'YYYY-MM-DD HH:mm') }}  →  2026-10-20 15:45
{{ formatDate(shipDate, '[Shipped] MMM D') }}  →  Shipped Oct 20
Printed {{ formatDate(now, 'MM/DD/YYYY') }}  →  Printed 10/20/2026
Token Prints Token Prints
YYYY 2026 HH / H 15 / 15 (24-hour)
YY 26 hh / h 03 / 3 (12-hour)
MMMM October mm 45 (minutes)
MMM Oct ss 00 (seconds)
MM / M 10 / 10 A AM / PM
DD / D 08 / 8 dddd / ddd Sunday / Sun
[text] the text as-is

Warning: Time values that include a time zone (…Z or …+02:00) are converted to the time zone of the computer that renders the PDF. If your system sends local warehouse times, it should send them without a zone ("2026-10-20T15:45:00"). Your integrator can confirm this.

Text

{{ upper(shipTo.city) }}  →  COLUMBUS
{{ lower('PREPAID') }}  →  prepaid
{{ pad(carrier.proNumber, 10, '0') }}  →  0078121007
{{ truncate(items[0].description, 20) }}  →  Organic cotton crew…
{{ substr(sscc, 1, 7) }}  →  0614141
{{ replace(orderNumber, 'SO-', '') }}  →  104412
{{ join(poNumbers, ' / ') }}  →  PO-780100 / PO-780137 / PO-780174
{{ concat(shipTo.city, ', ', shipTo.state) }}  →  Columbus, OH
{{ len(poNumbers) }}  →  3

Empty and missing values

coalesce returns the first value that is not empty. Use it for defaults:

{{ coalesce(notes, 'No special instructions') }}  →  No special instructions
{{ coalesce(shipTo.attention, 'Receiving') }}  →  Receiving
{{ item.hazmat ?? 'n/a' }}  →  n/a

?? only replaces missing values (null), while coalesce also skips empty text. A field sent as "" is skipped by coalesce but kept by ??.

Showing and hiding elements: Visible if

Behavior → Visible if takes a bare expression. The element prints only when the expression is "truthy":

Value Visible?
true, any non-zero number, any non-empty text, a non-empty list Yes
false, 0, empty text, missing, an empty list No
hazmat
notes
count(items) > 20
freightTerms == 'collect' && cod.amount > 0
page == pages

The last one prints an element only on the last page. A hidden element still takes up its space. Elements below it don't move up.

Tip: To show one of two texts, use one element with a condition inside it instead of two overlapping elements: {{ hazmat ? 'Contains dangerous goods' : 'No dangerous goods' }}.

Formatting for other languages

formatNumber, formatCurrency and formatDate follow the Document language set on the Report tab (see International documents). The same template prints like this in other languages (French and German use non-breaking spaces as separators, so numbers never break across lines):

# locale: fr-FR
{{ formatNumber(totalWeight, 1) }} kg  →  2 942,5 kg
{{ formatDate(orderDate, 'dddd D MMMM YYYY') }}  →  dimanche 18 octobre 2026
# locale: de-DE
{{ formatCurrency(declaredValue, 'EUR') }}  →  1.250,00 €
# locale: ko-KR
{{ formatDate(orderDate, 'YYYY년 M월 D일 dddd') }}  →  2026년 10월 18일 일요일
# locale: zh-CN
{{ formatDate(orderDate, 'YYYY年M月D日') }}  →  2026年10月18日
{{ formatCurrency(declaredValue, 'CNY') }}  →  ¥1,250.00
# locale: ja-JP
{{ formatCurrency(declaredValue, 'JPY') }}  →  ¥1,250

Recipes

Address block that skips an empty line. Put each line in the same text element, separated by Enter, and skip the attention line when it is empty:

{{ shipTo.name }}{{ shipTo.attention ? ' / Attn: ' + shipTo.attention : '' }}  →  Northwind Retail #1187
{{ shipTo.address1 }}  →  2250 Lakeshore Blvd

Singular and plural.

{{ cartons }} {{ cartons == 1 ? 'carton' : 'cartons' }}  →  2 cartons

Zero-padded document numbers (for example for a barcode that must be 10 digits):

{{ pad(carrier.proNumber, 10, '0') }}  →  0078121007

Totals with units.

Total: {{ formatNumber(sum(items, 'qty'), 0) }} units, {{ formatNumber(totalWeight, 1) }} lb  →  Total: 82 units, 2,942.5 lb

A "Page X of Y" footer with print date.

Page {{ page }} of {{ pages }} · Printed {{ formatDate(now, 'MM/DD/YYYY h:mm A') }}  →  Page 2 of 3 · Printed 10/20/2026 2:30 PM

A tracking link. In Behavior → Link: https://track.example.com/?pro={{ carrier.proNumber }}.

Conditional label text on a BOL.

{{ freightTerms == 'prepaid' ? 'Freight charges are PREPAID' : 'Freight charges are COLLECT' }}  →  Freight charges are PREPAID

When something goes wrong

These are wrong:

formatNumber(weight, 2
{{ 'Door + 3 }}
{{ items[0.sku }}

More fixes are in Troubleshooting.

← 5. Elements reference7. Tables →