Guide

CEL cheat sheet

Routing, transforms, conditions, and payloads are CEL expressions (Google's Common Expression Language). They compile once when the flow builds — so a typo fails at startup, not in production — and run read-only over each message. The upstream spec is terse; here's everything you actually need.

In scope

What an expression can see.

Every message-driven block evaluates against the same activation. These are the variables in scope:

VariableTypeWhat it is
bodyJSON valueThe decoded message body — usually a map, sometimes a list/scalar. Rewritten by set-payload.
varsmapPer-message variables. Read here; write with set-variable. Sources seed it (see below).
envmapResolved values of the flow's declared env: entries — e.g. env.HTTP_PORT. Only declared keys exist.
eventIDstringStable unique id for this message.
correlationIDstringCaller-supplied id for grouping related messages (optional).
From the source

What sources put in vars.

FromVariableExample
http sourcevars.methodvars.method == "POST"
http sourcevars.query (always a map)has(vars.query.currency)
http path /orders/{id}vars.<param>vars.id
http captured headervars["X-Header"]vars["X-Tenant"]
cron source payloadnowstring(now)
error pathvars.errorvars.error.message · .flow · .block
Copy-paste

Recipes.

Every snippet below is lifted from a working sample in samples/.

Build a string · coerce types

"hello, " + body.name + "!"
"processing " + string(size(body.orders)) + " orders"

Safe access with a default

has(vars.query.currency) ? vars.query.currency : "USD"

Route on method or value (switch / if)

vars.method == "POST"
body.amount >= 1000.0
size(body.orders) > 0

Build a JSON object (set-payload)

{"orderId": vars.id, "currency": vars.currency, "status": "found"}

Reach into nested fields & dashed headers

body.current.temperature_2m
vars["X-Tenant"]

Read environment variables

"listening on " + env.HTTP_PORT

Iterate a list (foreach)

items: body.orders        # the list to split
as: order                 # each item -> vars.order
vars.order.amount >= vars.threshold

Recover from an error (error path)

{"error": vars.error.message,
 "failedBlock": vars.error.block,
 "flow": vars.error.flow}
Gotchas worth knowing. JSON numbers are always double (float) — wrap with int(...) when you need a whole number. Use has(map.key) before reading a key that might be missing, or the expression errors. Only env: entries you declare are in scope — an undeclared env.X fails at build time. Expressions never mutate state: read vars/body here, and change them with set-variable / set-payload. Use bracket syntax (vars["X-Tenant"]) for keys with dashes.