Expression Language
Expressions are strings. Non-string primitives (42, true, [], {}) are treated as literals.
Scopes
| Scope | Available in | Description |
|---|---|---|
event.<field> | actions, projections (derive), listeners | Event payload field |
event.ts | all | Event timestamp (epoch ms) — reserved |
event.entity_id | all | Routing entity ID — reserved |
event.type | all | Event type string — reserved |
entity.<field> | actions (core/side_effects), preflight | Current entity state (dot notation) |
input.<field> | actions (entity_id_expr), queries | Action input or query parameter |
intent.<field> | actions | Alias for input.<field> |
state.id | actions | Current entity ID |
resources.<type>["<id>"].<field> | all | Static resource lookup |
it.<field> | find, filter, remove, for_each | Iterator variable |
item.<field> | emit_each, for_each | Iterator variable |
<var>.<field> | after find … as: var or clone … as: var | Bound variable |
project.<field> | actions | Platform-level shared state |
global.<field> | actions | Global read-only state |
event.<field> in rules vs projections
In rules: event.amount reads from the intent payload directly.
In projection derive:: event.amount and event.payload.amount are both accepted (the latter is a backwards-compat alias).
Never use event.payload.amount in rules — it always returns null.
Operators
== != > < >= <= && ||
+ - * /
!expr (logical not: !contains(entity.tags, "x"))
cond ? a : b (ternary)
val ?: default (null-coalesce)
entity['path'] (bracket access)
array[0] (index access)Built-in functions
| Function | Description |
|---|---|
size(array) | Array length |
sum(array) | Sum of numeric values |
contains(array, value) | Check if array contains value |
map(array, expr) | Transform array |
filter(array, expr) | Filter array (keep matching) |
max(a, b) | Maximum of two values |
min(a, b) | Minimum of two values |
floor(value) | Floor of a number |
concat(a, b, …) | Concatenate strings |
join(array, sep) | Join array to string |
format_date(ts) | Format epoch ms timestamp |
is_prev_day(d1, d2) | Check if two timestamps are on different days |
shard(expr, N) | Map expression to shard ID 0..N-1 |
lookup(stream, id_expr).<field> | Cross-entity lookup (projections only) |
LOOKUP_FIELD('stream', id, '/path') | Cross-stream field read (rule expressions / preflight) |
FORBIDDEN expressions
The following are rejected at compile time with VERR_NON_DETERMINISTIC_EXPRESSION or VERR_SPEL_FORBIDDEN_FUNCTION:
now() random() uuid() Math.random
shuffle() nanoTime() System.* Runtime.*
Process.* Thread.* Runnable.* getClass
forName eval exec
http.* redis.* db.* kafka.*
s3.* stripe.* env.* secret.*All IDs must be deterministic. Build them with concat:
value: "concat('notif_', event.user_id, '_', event.show_id)"