# Expression reference

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](/docs/builder/bindings) for the editing workflow and [Action reference](/docs/builder/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.

| Binding | Result 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:

```text
{{ 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.

```text
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.

```text
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.

```text
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`.

```text
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"`.

```text
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.

```text
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.

```text
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`.

```text
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.

```text
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`.

```text
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`.

```text
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`.

```text
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.

```text
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.

```text
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.

```text
{{ (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) }}
```

| Method | Return 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.

| Method | Return 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

| Limit | Default |
| --- | --- |
| Source length inside one binding's delimiters | 4,096 UTF-16 code units, including whitespace |
| Bracket nesting and parsed expression depth | 256 levels |
| Shared evaluation work per expression segment | 200,000 operations |
| Produced string length | 262,144 UTF-16 code units |
| Array-size guard on array literals, map, concat, flat, and split results | 50,000 items |
| `flat` depth | 10 |
| `round` numeric precision | Clamped 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

| Diagnostic | Typical cause and correction |
| --- | --- |
| `empty_expression` or `syntax_error` | Empty delimiters or malformed expression; supply one complete expression. |
| `unknown_identifier` | Misspelled root/helper/local name or unavailable global. |
| `null_member` | Read through a nullish value; use `?.` and an appropriate fallback. |
| `unknown_member` | Unsupported method or property for the value's type. |
| `forbidden_member` | Blocked property or invalid computed key. |
| `not_callable` | Called a non-function or supplied a non-arrow callback. |
| `not_a_value` | Tried to expose a function as data. |
| `forbidden_syntax` | Used a statement, mutation, regular expression, or other unavailable construct. |
| `expression_too_long`, `expression_too_deep` | Simplify the authored expression. |
| `budget_exceeded`, `result_too_large` | Reduce data or intermediate results; calculate upstream where appropriate. |
| `eval_error` | Invalid 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](/docs/builder/preview) with empty, missing, invalid, and representative large data, not only the populated happy path.
