Dynamic bindings
Anywhere text appears you can write {{ expression }}; binding fields in the inspector hold an expression directly. The Data panel and the field picker insert them for you, so most users never type one.
The expression language
Small, safe, and statically checkable. No JavaScript, no eval, no prototype access.
client.firstName
portfolio.holdings[0].name
portfolio.holdings.value → list of every holding's value
sum(portfolio.holdings.value)
portfolio.value | currency:"GBP":0
client.dateOfBirth | date:"d MMMM yyyy"
client.isRetired ? "Retired" : "Working"
client.middleName | default:"—"Operators
== != < <= > >= + - * / %, and or not, in, a ? b : c. Equality is forgiving ("7" == 7), comparisons on null are false, division by zero yields null, never an exception.
Formatters (pipes)
| Pipe | Example | Output |
|---|---|---|
currency[:code][:decimals] | value | currency:"EUR":0 | €12,346 |
number[:decimals] | n | number:1 | 1,234.6 |
percent[:decimals][:scaled] | 0.125 | percent:1 | 12.5% |
date[:pattern] | d | date:"dd MMM yyyy" | 20 May 2026 |
datetime[:pattern] | d | datetime:"HH:mm" | 14:30 |
boolean[:yes][:no] | b | boolean:"Yes":"No" | Yes |
upper, lower, title, initials, truncate:n | name | truncate:20 | … |
default:x | note | default:"None" | fallback when empty |
join:sep, length, first, last, take:n, sortBy:field:dir, filter:field:value, pluck:field | list helpers | |
round:n, abs, pluralize:one:many |
Date patterns: yyyy yy MMMM MMM MM M dd d EEEE EEE HH H hh h mm ss a, or the named styles short, medium, long, full. Locale, currency and time zone come from the template settings and can be overridden per render.
Functions
sum count avg min max first last length coalesce isEmpty isNotEmpty contains startsWith endsWith concat round abs number string if now year daysBetween.
Globals
page.number, page.total (resolved per page), now, theme.name, theme.logo, theme.colors.*, theme.meta.* (anything you put in the theme's meta, e.g. company name or regulatory text).
Scopes
Inside a repeater or table, the current item is available under the alias you chose (holding, row…) plus item, index (1-based), first, last, count. Aliases shadow outer names; validation warns if an alias hides a data source.
Static type checking
typeCheck(expr, scope) resolves the result type from the schemas without any data, which powers the inspector's warnings, the picker's format suggestions and validate():
import { typeCheck, createSchemaScope } from "@broadpaper/core";
typeCheck("client.firstName | currency", createSchemaScope(dataSources));
// { type: "string", issues: [{ code: "type-mismatch", message: 'Formatter "currency" expects number or currency but received string' }] }