Octov0.11.7
ReferenceCEL

CEL Expressions

The Common Expression Language in Octo: where it runs and how to try it.

Octo uses the Common Expression Language (CEL) wherever a flow needs to compute a value: transforming a body, testing a condition, building a log message, binding a SQL argument. CEL is a small, typed expression language in which every program is a single expression producing a single value. It is deliberately not Turing-complete: there are no loops, no statements, no user functions, and no side effects, so an expression always terminates and its cost is predictable.

Expressions are compiled once, when the flow is built at startup, so a malformed expression fails the deployment rather than a message in production. They are then evaluated per message against the message's variables: the decoded body, the vars map, eventID, correlationID, the resolved env, and now, the evaluation time. The result is always a JSON-native value (object, array, string, number, boolean, or null), matching the runtime's JSON message contract. See Octo Extensions for the full variable list and the custom functions Octo adds, and Extension Libraries for the cel-go utility libraries (string manipulation, list reshaping, base64, math, sets, and regex) available in every expression.

Where expressions appear

Any block setting documented as an expression is CEL, evaluated against the current message. The most common places:

WhereWhat the expression does
set-payloadvalue becomes the new body.
if / switchcondition and each case's when must evaluate to a boolean.
foreachitems must evaluate to a list.
logmessage is an expression, which is why literal text is quoted twice (see below).
set-variablevalue is the expression stored in vars.
sqlEach entry of args binds one statement placeholder.
rest and other connector blocksURL, header, and body settings.
validateEach rule's expr is an assertion.
Source payloadsA cron source's payload expression builds the initial body, with now and settings in scope but no message variables yet.
An ai-agent tool's authorizeDecides whether a call needs a person, with the message scope plus tool (name, id) and input, the call's decoded arguments. See Tool authorization.
process:
  - type: if
    name: any-orders
    condition: "size(body.orders) > 0"
    then:
      process:
        - type: log
          settings:
            message: '"processing " + string(size(body.orders)) + " orders"'

The YAML quoting rule

Expression settings hold CEL source code, and YAML strips one layer of quoting before CEL ever sees the value. To produce the string hello, the CEL expression must be "hello", so in YAML you write it wrapped in single quotes:

settings:
  message: '"hello"'             # CEL sees "hello"  -> the string hello
  message: '"hi " + body.name'   # concatenation with a literal

Written as message: "hello" or message: hello, CEL receives the bare identifier hello and fails to compile with undeclared reference to 'hello'. Expressions that start with a variable need no inner quotes: condition: "size(body.orders) > 0" works as-is.

Experimenting with octo eval

The octo eval subcommand evaluates an expression without a config, printing a JSON envelope with the result:

bin/octo eval --expr '1 + 2'
# {"ok":true,"result":3}

bin/octo eval --expr '"hi " + body.name' --data '{"name": "Ada"}'
# {"ok":true,"result":"hi Ada"}

--data binds a JSON object to body (piped stdin works too), --vars binds vars, and --env binds env:

bin/octo eval --expr 'body.orders.filter(o, o.amount >= vars.min)' \
  --data '{"orders": [{"id": 1, "amount": 50}, {"id": 2, "amount": 250}]}' \
  --vars '{"min": 100}'
# {"ok":true,"result":[{"amount":250,"id":2}]}

bin/octo eval --expr 'env.API_BASE_URL + "/orders"' \
  --env '{"API_BASE_URL": "https://api.example.com"}'
# {"ok":true,"result":"https://api.example.com/orders"}

A compile or evaluation error comes back in the same envelope with "ok":false, and the exit code stays 0, so you can iterate freely:

bin/octo eval --expr 'body.missing'
# {"ok":false,"result":null,"error":"evaluate expression: no such key: missing"}

On the command line the expression is passed in single quotes, so CEL string literals keep their double quotes, the same shape you will paste into YAML.

In this section

  • Language Tour: CEL by example, covering types, operators, strings, lists, maps, conversions, timestamps, and equality.
  • Macros: the comprehension macros filter, map, all, exists, and exists_one.
  • Extension Libraries: the cel-go utility libraries Octo enables (strings, lists, encoders, math, two-variable comprehensions, sets, regex), and what is deliberately left out.
  • Octo Extensions: the variables in scope and the custom functions Octo registers (toJson, fromJson, toFormData, fromFormData, templateResource).

On this page