Template syntax.
The dialect used by the transform action and the filter field references. Looks like JSON, allows unquoted keys, and substitutes a handful of $-rooted refs with values pulled from the running pipeline context.
Example
One template, one event.
{
id: $event.id,
email: $event.payload.email,
plan: $entity.user.plan,
intent: $classify.intent,
summary: $summarize.text,
company: $enrich.company.name,
sent_at: now()
}Renders to:
{
"id": "evt_01HZK4...",
"email": "ada@example.com",
"plan": "growth",
"intent": "hot_lead",
"summary": "Asking about pricing for the enterprise tier.",
"company": "Acme",
"sent_at": "2026-05-21T14:03:11.512Z"
}The six roots
What you can reference.
$eventthe full event envelope (id, type, received_at, payload)
populated by · ingestion (always present)
$entityRecord<kind, Record<string, unknown>> — the identity-graph rows merged in by enrich.entity / upsert.entity
populated by · enrich.entity, upsert.entity
$enrich{ person?, company? } — internal-tier derived facts
populated by · enrich.person, enrich.company
$classifyRecord<dimension, string> — LLM-chosen labels
populated by · classify
$summarize{ text: string } — the model's short summary
populated by · summarize
$translate{ text: string } — the translation
populated by · translate
Paths
Dotted, identifier segments only.
After the root, address into nested objects with dots: $event.payload.email, $entity.user.account_owner. Segments must be valid identifiers (letters, digits, underscores, no array index access yet). A path that doesn't resolve substitutes undefined — which JSON-encodes as null.
now()
The only function.
now() substitutes an ISO timestamp (UTC) at render time. Useful for stamping events with a delivery time, or as a downstream sent_at field.
How substitution works
Four passes, in order.
now()→ JSON-encoded timestamp.- Dotted refs (
$event.foo.bar) → JSON-encoded value at that path. - Bare roots (
$event,$entity) → JSON-encoded object. - Bare object keys → quoted, so the final string parses as JSON.
Values are JSON-encoded on substitution — so a string field comes out quoted, a nested object expands to {...}, and the final rendered string parses cleanly. This is structural by design; the dialect is not meant for prose interpolation like Hello $event.name (you'd get Hello "Ada").
In filter conditions
Same refs, no JSON encoding.
Filter conditions reference the same $-rooted paths in their field column. Values are compared as strings for equality, coerced to numbers for ordered ops, and split on commas for in. Same resolver, different output mode.