Sats docs

Expressions

A small, safe expression language. It cannot call Java, access the file system, loop forever or allocate unbounded memory.

Where expressions appear

Values

null, booleans, numbers (decimal, BigDecimal internally, never double for money), strings (single or double quotes), lists, objects. Dates are ISO 8601 strings in data ("2026-10-11", "2026-10-11T14:30:00+02:00") and are parsed by date filters.

Paths and scope

Built-in variables

Variable Value
@page Page number in the whole document (counted pages only)
@pageCount Total counted pages
@partPage, @partPageCount Same, within the current part
@index 0-based index in the innermost repeat
@first, @last Booleans for the innermost repeat
@renderDate From render options; defaults to the date of the request in Europe/Oslo

@page and @pageCount are only allowed in page templates, PDF part overlays and toc. Using them in a section is a validation error (page_variable_in_flow), because the value would change the layout that decides it.

Operators (lowest to highest precedence)

| (filter) · ?? · || · && · == != · < <= > >= · + - · * / % · unary ! - · member/index.

The filter pipe binds loosest, so it applies to the whole expression on its left: line.qty * line.price | currency('NOK') formats the product. Use parentheses to filter a part.

+ on two strings concatenates. Comparisons between different types are false (no coercion). Division by zero gives null. Arithmetic with a null operand gives null; with other wrong types it gives null (strict mode: type_mismatch).

Truthiness (visibleIf, !, &&, ||, if): null, false, 0, "" and the empty list are falsy; everything else is truthy. && and || return one of their operands.

Inside {{ }} values become text as follows: null → empty, numbers → plain decimal (2.50 → 2.5), booleans → true/false, lists → items joined with , .

Filters

value | name(args). Filters chain left to right. Every filter except default returns null for a null input, so x | currency('NOK') | default('-') works.

Filter Example Result
currency(code) 1234.5 | currency('NOK') 1 234,50 kr (CLDR format of the template locale)
number(decimals) 3 | number(2) 3,00
percent(decimals) 0.25 | percent(0) 25 %
date(pattern) '2026-10-11' | date('d. MMMM yyyy') 11. oktober 2026
upper, lower, trim
default(value) x | default('-') - when x is null or empty string
truncate(n) 'abcdef' | truncate(4) abc… (at most n characters including …)
join(sep) tags | join(', ')
round(n) half-up rounding, returns a number

Number, currency and percent formats follow CLDR for the template locale and round half-up.

Patterns use java.time format letters. Locale comes from the template's locale.

Functions

sum(list, 'expr'), count(list), min(list, 'expr'), max(list, 'expr'), filter(list, 'expr'), first(list), last(list), len(stringOrList), if(cond, a, b).

The second argument of list functions is an expression evaluated per item with the item's fields in scope: sum(order.lines, 'qty * price').

Limits

Expression length ≤ 2000 chars; evaluation step budget 10 000 per expression; list functions over at most 100 000 items. Exceeding a limit is an error with the template path.

Errors

Parse errors are reported at template validation (expression_syntax, with template path and column). Runtime errors in strict mode (unknown_path, type_mismatch) carry the data path.