Expressions
Report Builder ships a hand-written expression language. There is no eval, new Function, or string-form setTimeout — every expression goes through a typed tokenizer → recursive-descent parser → AST walker. All component props that can be dynamic accept an expression by prefixing a = sign; anything else is treated as a literal.
Most props panels already switch to the expression editor with a single fx toggle; what you see in the playground below is the same editor component used in the builder.
Playground
Namespaces
Only the following identifier roots are recognised. Anything else is an unknown identifier.
Datasources.<name>/Datasources.<name>.<field>— rows or a projected field arrayParameters.<name>(with optional.path.to.field) — host-owned input valuesVariables.<name>(with optional.path) — in-report writable state, set by drill-through / click actionsBuiltInFields.<name>— read-only host constants supplied viainitReportBuilderor the componentbuiltInFieldspropField.<name>— the current row being rendered (inside a table cell, chart formatter, row visibility expression, etc.)RowGroup('name').key/.keys/.values/.Fields.<field>/.Variables.<name>— access a row-group bucketColGroup('name')— same shape as RowGroup, for column-grouping contexts
Worked Examples
Aggregates
Aggregating over a whole datasource, typically in head/footer rows and KPI text.
FormatCurrency(Sum(Datasources.orders, 'revenue'), '$', 0)
Round(Avg(Datasources.employees, 'performance'), 1)
Count(Datasources.employees, 'email')
Conditionals
Branching output from a single expression. IIf is the workhorse; Switch is cleaner for many-way dispatch.
IIf(Field.status == 'paid', '✓ Paid', IIf(Field.status == 'pending', '⏳ Pending', '✗ Other'))
Switch(Field.status, 'paid', 'Completed', 'pending', 'In review', 'cancelled', 'Dropped', 'Other')
IsEmpty(Parameters.region) || Field.region == Parameters.region
String / Number formatting
Turn raw values into display strings. All formatters are null-safe and return empty strings on garbage input.
FormatCurrency(Field.revenue, '€', 2)
FormatPercent(Field.revenue / Field.target, 1)
Concat('INV-', PadLeft(Field.id, 6, '0'))Dates
Date math and formatting. Dates are ISO strings internally — the functions accept any Date-parseable string.
Today()
DateDiff('day', Field.orderDate, Today())FormatDate(Field.orderDate, 'YYYY-MM-DD')
Variable paths
Variables are writable state. Anything drilled from a table or chart lands in a Variable and can be re-read by expressions elsewhere.
Variables.selectedOrder.revenue
!IsEmpty(Variables.selectedOrder)
Built-in fields
Host-supplied constants (current user, tenant, feature flags). Combined from initReportBuilder registrations and per-instance props.
BuiltInFields.CurrentUser.name
BuiltInFields.Env == 'production'
Row-group scope
Table row groups expose a per-bucket frame in expressions. Inside a group-level formatter (for example a group header text or footer cell), you address the enclosing group with RowGroup('name').
RowGroup('region').key— the single grouping key (e.g."North") when the row group has a single key.RowGroup('region').keys— array form when the group is keyed by multiple fields.RowGroup('region').values— the rows inside this bucket. Feed this directly intoSum / Count / Avg.RowGroup('region').Fields.revenue— shortcut for the most-recent row's field value in that bucket.RowGroup('region').Variables.myVar— a per-bucket variable computed by the group engine.
Rules & gotchas
- No unsafe eval. Every expression is parsed into an AST; there is no string execution.
- Missing property paths return null. Forbidden path segments (
__proto__,prototype,constructor) also return null. - Type mismatches surface as warnings. If a field expects
booleanand your expression returnsstring, the editor shows an error. - Optional-filter pattern. Always write
IsEmpty(Parameters.x) || <clause>so a blank parameter disables the clause instead of excluding every row. - Parameters are read-only inside the viewer. Click actions, drills, and chart clicks write to
Variables. Parameters only change via the parameter panel's Apply button. - Async built-in fields. A built-in field may be a Promise-returning getter. The evaluator awaits it, so the expression simply resolves when the value is ready — no special syntax required.