Skip to content

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.

text
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)

PipeExampleOutput
currency[:code][:decimals]value | currency:"EUR":0€12,346
number[:decimals]n | number:11,234.6
percent[:decimals][:scaled]0.125 | percent:112.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:nname | truncate:20
default:xnote | default:"None"fallback when empty
join:sep, length, first, last, take:n, sortBy:field:dir, filter:field:value, pluck:fieldlist 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():

ts
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' }] }