Skip to content

Start here

Expression reference

Every expression helper, supported array and string method, typed result, fallback, error, and evaluation limit.

Use expressions in binding-capable properties and action fields to calculate a value from current app state. Enter the expression between {{ and }}. This reference covers the expression language; see Bindings for the editing workflow and Action reference for fields that evaluate expressions.

Values, types, and scope

The available roots are app, components, and dataSources. Read a component's documented published fields, such as components.Search.value, and a declared source's data, such as dataSources.Orders.data. An identifier is case-sensitive. A callback's parameters, or a repeated component's local item and index, apply only within their scope and can shadow another name.

A single expression preserves its result type. Surrounding whitespace does not change this. Mixed text produces a string instead.

BindingResult and type
{{ 2 + 3 }}5, number
{{ false }}false, boolean
{{ [1, 2] }}[1, 2], array
{{ ({ amount: 2 }) }}{ "amount": 2 }, object
Total: {{ 2 + 3 }}"Total: 5", string
Selected: {{ null }}"Selected: ", string
{{ null }}null

Mixed text renders null and undefined as empty text and objects and arrays as compact JSON. An ordinary string without a completed binding remains literal text. For an object literal inside a binding, parentheses and a space before the closing delimiter keep the object and binding delimiters clear.

Use optional member access for values that may not exist and nullish fallback when zero, false, and empty text are legitimate values:

{{ components.Orders.selectedRow?.amount ?? 0 }}
{{ dataSources.Orders.data ?? [] }}
{{ app.title ?? 'Untitled' }}

?? falls back only for null or undefined. || also falls back for false, 0, NaN, and empty text. Empty arrays and objects are truthy. Reading a missing ordinary object property returns undefined; reading a property of null or undefined without a guard produces a binding error. Array indexing is zero-based. Array and string .length are available; string length counts UTF-16 code units, so some visible characters occupy more than one unit.

All 14 helper functions

In the signatures below, value can be any expression value, array means an array, object means a plain object, ? marks an optional argument, and ... accepts separate arguments. Types describe returned expression values; they do not make an incompatible input valid for a component property. Omitting a single value argument passes undefined, so its documented undefined behavior applies. Extra arguments are ignored unless the signature accepts them.

len(value) → number

Returns the length of a string or array, or the number of an object's own enumerable keys. Other inputs return zero.

len('hello')                     → 5
len([10, 20])                     → 2
len({ ready: true, count: 2 })     → 2
len(null)                        → 0
len(undefined)                   → 0
len(42)                          → 0

An array's length includes empty positions. Object counting consumes work proportional to its number of keys.

keys(value) → string[]

Returns an object's own enumerable keys using ordinary JavaScript key ordering. Arrays, strings, numbers, booleans, and nullish values return an empty array. This helper does not turn an array into its index names.

keys({ name: 'Ada', active: true }) → ['name', 'active']
keys([10, 20])                     → []
keys(null)                         → []

Use keys(app.settings).map(key => key) when building a list of setting names. Numeric-looking object keys can appear before other keys; do not use key order as an explicit business sort order.

values(value) → unknown[]

Returns the values corresponding to an object's own enumerable keys in the same ordering as keys. Arrays and other non-object inputs return an empty array. Existing null values are preserved.

values({ first: 10, second: null }) → [10, null]
values([10, 20])                    → []
values(undefined)                   → []

To sum an object's numeric values, use sum(values(app.totals)). Both the object scan and the sum consume the expression's work budget.

number(value) → number

Uses JavaScript numeric conversion. Numeric text becomes a number; empty or whitespace-only text, null, and false become zero; true becomes one. undefined and text that is not numeric produce NaN.

number('12.5')  → 12.5
number('')      → 0
number(null)    → 0
number(true)    → 1
number('oops')  → NaN

NaN is a numeric result, not a binding diagnostic. In particular, number('oops') ?? 0 still returns NaN. Validate user input before using it in a calculation. An unusual object that cannot be converted can instead produce an evaluation error. number([5]) is 5, while number([5, 6]) is NaN; prefer explicit scalar inputs.

string(value) → string

Converts scalars to text and serializes objects and arrays as compact JSON. Unlike mixed-template interpolation, explicit conversion of nullish values produces the words "null" and "undefined".

string(42)                → '42'
string(false)             → 'false'
string(null)              → 'null'
string(undefined)         → 'undefined'
string({ amount: 2 })     → '{"amount":2}'
string([1, null])          → '[1,null]'

A serialization failure uses "[object]" as its fallback. The string-size limit still applies; object and array serialization also consumes work proportional to the resulting text. Use json when readable indentation matters.

round(value, digits?) → number

Converts the first argument with number and rounds it using JavaScript Math.round behavior. A numeric digits argument is clamped between zero and 12. Omitted or non-numeric digits means zero decimal places. Use an integer for predictable decimal-place selection.

round(12.345, 2)  → 12.35
round('2.6')      → 3
round(12.6, -2)   → 13
round(12.345, '2') → 12
round(-1.5)        → -1
round(null)        → 0
round('oops', 2)   → NaN

Negative precision does not round to tens or hundreds. Floating-point arithmetic can affect decimal boundaries; this helper does not create an exact decimal currency type. A numeric NaN precision produces NaN.

abs(value) → number

Returns the absolute value after numeric conversion.

abs(-4)       → 4
abs('-3.5')   → 3.5
abs(null)     → 0
abs('oops')   → NaN

It follows the same conversion and invalid-number behavior as number.

min(...values) → number

Converts each argument to a number and returns the smallest. With no arguments, returns Infinity. Any NaN argument makes the result NaN.

min(3, '2', null)  → 0
min(3, 8)          → 3
min()               → Infinity
min(3, 'oops')      → NaN
min([3, 8])         → NaN
min(...[3, 8])      → 3

An array is not automatically unpacked. Use argument spread for an array of values, and decide how to handle an empty array before displaying its minimum.

max(...values) → number

Converts each argument to a number and returns the largest. With no arguments, returns -Infinity. Any NaN argument propagates.

max(3, '8', null)  → 8
max()               → -Infinity
max(3, 'oops')      → NaN
max(...[3, 8])      → 8

As with min, pass separate values or spread an array. Numeric strings and booleans are converted, so filter or validate data first when these should not count as numbers.

sum(array) → number

Adds an array's values after numeric conversion, treating null and undefined elements as zero. An empty array, or an input that is not an array, returns zero. Invalid numeric text makes the whole sum NaN.

sum([2, '3', null, undefined]) → 5
sum([true, false, 2])           → 3
sum([])                        → 0
sum('1,2')                     → 0
sum([2, 'oops'])               → NaN

For records, select the numeric field first: sum((dataSources.Orders.data ?? []).map(row => row.amount)). The helper traverses the entire array and shares the binding's work budget with the mapping operation.

first(array) → unknown or undefined

Returns the first array element. Empty arrays and inputs that are not arrays return undefined. A first element that is null remains null.

first([10, 20]) → 10
first([null, 2]) → null
first([])        → undefined
first('hello')   → undefined

A safe first-row title is first(dataSources.Orders.data ?? [])?.title ?? 'No orders'. This does not select the first visible table row unless the source's ordering and the table's ordering match.

last(array) → unknown or undefined

Returns the final array element, preserving its type and null value. Empty arrays and non-array inputs return undefined.

last([10, 20])  → 20
last([1, null]) → null
last([])        → undefined
last(null)      → undefined

Use an explicit source order when the last element is intended to represent the newest record.

json(value) → string

Serializes a value as JSON with two-space indentation. The returned value is text, suitable for a text or code display; it is not an object binding.

json({ amount: 2 }) → '{\n  "amount": 2\n}'
json(undefined)     → 'null'
json(NaN)           → 'null'
json([undefined, 2]) → '[\n  null,\n  2\n]'

Undefined object properties are omitted; undefined array entries become null. Unlike string, a serialization exception produces an evaluation error. The string-size cap and serialization work budget apply, so a large value can fail even when its source data is available.

parseJson(value) → parsed value or null

Parses a string containing JSON. The result can be an object, array, string, number, boolean, or null. Invalid JSON and non-string inputs return null without a parse diagnostic.

parseJson('{"amount":2}') → { amount: 2 }
parseJson('[1,2]')         → [1, 2]
parseJson('42')            → 42
parseJson('true')          → true
parseJson('null')          → null
parseJson('not JSON')      → null
parseJson({ amount: 2 })   → null

Valid JSON null and invalid JSON share the same result. parseJson(components.JsonInput.value) ?? {} can supply an object fallback, but it does not verify that a valid parsed value is an object. Parsing charges the input string's length against the work budget; a budget failure is a binding error rather than the null fallback.

Supported array methods

Methods work on arrays, and callback methods accept expression-body arrow functions with plain identifier parameters. The ordinary callback receives (item, index); reduce receives (accumulator, item, index) and a sort comparator receives (left, right). Callback block bodies, destructured/default/rest parameters, and asynchronous functions are unavailable.

{{ (dataSources.Orders.data ?? []).filter(row => row.status === 'open') }}
{{ [10, 2, 4].sort((left, right) => left - right) }}
{{ [1, 2, 3].reduce((total, item) => total + item, 0) }}
MethodReturn and behavior
map(callback)New array containing each callback result. A callback cannot return a function value.
filter(callback)New array of elements with truthy callback results.
find(callback)First matching element, or undefined.
findIndex(callback)First matching zero-based index, or -1.
some(callback)Whether any element matches; false for an empty array.
every(callback)Whether every element matches; true for an empty array.
reduce(callback, initial?)Accumulates values. Without an initial value, starts with the first element; an empty array then produces an evaluation error.
slice(start?, end?)New array over the selected range; end is exclusive and negative indexes count from the end. Only numeric arguments are used.
includes(value)Boolean membership check; recognizes NaN and uses reference identity for objects.
indexOf(value)First index, or -1; does not match NaN.
concat(...values)New array; array arguments are expanded one level, other values appended.
join(separator?)String, comma-separated by default; nullish elements become empty text and object elements become compact JSON.
flat(depth?)New flattened array; default depth one, maximum accepted depth 10.
sort(compare?)Sorted copy. Default ordering compares text representations; provide a numeric-returning arrow comparator for numeric ordering. A non-number comparator result is treated as zero.
reverse()Reversed copy.

sort and reverse preserve the source array. These methods do not write app state. The membership methods do not accept a starting-position option. Flattening beyond depth 10 fails; flat(Infinity) is therefore unavailable. Use an initial value with reduce when data may be empty.

Supported string methods

String methods operate on strings and are case-sensitive unless you normalize the string first. Regular expressions and replacement callbacks are unavailable.

MethodReturn and behavior
toLowerCase(), toUpperCase()Converted string.
trim()Removes whitespace from both ends.
split(separator?)Array split on a literal string; absent or non-string separator returns a one-element array containing the original string. No result-limit argument.
includes(text), startsWith(text), endsWith(text)Boolean; non-string search values return false. No position argument.
slice(start?, end?)String range; numeric indexes only, exclusive end, negative indexes allowed.
replace(search, replacement)Replaces the first literal-string match using JavaScript replacement-string rules. Both arguments must be strings; otherwise leaves the string unchanged.
replaceAll(search, replacement)Replaces all literal-string matches; the same string-argument rules apply.
padStart(length, fill?)Pads on the left, with spaces by default. Only finite numeric target lengths are used; negative lengths behave as zero, and the target is bounded by the string-size limit.
repeat(count)Repeats a string. Finite numeric counts are truncated toward an integer; a non-numeric or non-finite count is treated as zero. Negative counts and oversized results fail.
charAt(index?)One UTF-16 code unit, or empty text outside the range; a non-number index defaults to zero.
indexOf(text)First matching index, or -1; non-string search returns -1. No starting-position argument.

For example, {{ (components.Search.value ?? '').trim().toLowerCase() }} prepares a case-normalized search string. Use number(components.Count.value) when a method needs a numeric argument; passing '3' to repeat does not repeat three times.

Operators and boundaries

Supported operators are arithmetic + - * / % **, comparisons == != === !== < <= > >=, logical && || ??, unary ! - + typeof, and the conditional test ? yes : no. Logical and conditional operators evaluate only the branch they need. Array/object literals, template literals, computed member access, optional member access, and array spread are supported.

Prefer strict equality when values have known types: 2 === '2' is false while 2 == '2' is true. Division by zero can produce Infinity; this is a numeric result rather than a binding diagnostic. The constants undefined, NaN, and Infinity are available. typeof app.missing can return "undefined"; typeof an undeclared identifier still reports an unknown identifier error.

Expressions can read permitted own properties and invoke the documented helpers and methods. They cannot access browser globals, issue network requests, assign values, create classes, run loops or statement blocks, use new, execute regular expressions, or call arbitrary host functions. The member names __proto__, constructor, and prototype, and inherited object methods, are blocked. A function or method itself cannot be used as a final binding value; call it to obtain a result.

Exact default limits

LimitDefault
Source length inside one binding's delimiters4,096 UTF-16 code units, including whitespace
Bracket nesting and parsed expression depth256 levels
Shared evaluation work per expression segment200,000 operations
Produced string length262,144 UTF-16 code units
Array-size guard on array literals, map, concat, flat, and split results50,000 items
flat depth10
round numeric precisionClamped to 0–12

Work includes expression-node visits, element processing, callback evaluation, and charged string or JSON processing. A large sort or nested map can exceed the work budget before reaching a size limit. Each expression segment has its own evaluation budget; a mixed template's combined string also has the 262,144-unit limit. A mixed template exceeding that output limit returns truncated text with an error diagnostic. The limits are guards, not a promise that every combination up to a listed size can be calculated.

The early nesting check counts bracket characters even inside strings, so pathological bracket-heavy text can reach the nesting guard. Move large raw text into data instead of embedding it directly in an expression.

Diagnose a failed binding

DiagnosticTypical cause and correction
empty_expression or syntax_errorEmpty delimiters or malformed expression; supply one complete expression.
unknown_identifierMisspelled root/helper/local name or unavailable global.
null_memberRead through a nullish value; use ?. and an appropriate fallback.
unknown_memberUnsupported method or property for the value's type.
forbidden_memberBlocked property or invalid computed key.
not_callableCalled a non-function or supplied a non-arrow callback.
not_a_valueTried to expose a function as data.
forbidden_syntaxUsed a statement, mutation, regular expression, or other unavailable construct.
expression_too_long, expression_too_deepSimplify the authored expression.
budget_exceeded, result_too_largeReduce data or intermediate results; calculate upstream where appropriate.
eval_errorInvalid operation such as reducing an empty array without an initial value.

Rendering reports the diagnostic without crashing the app. A failed standalone expression yields undefined; mixed text can retain surrounding successful segments. An action's failed binding instead stops its remaining action chain. Check Preview with empty, missing, invalid, and representative large data, not only the populated happy path.