7.5 Conditions, ${...} interpolation, and captured variables

Three small sub-languages thread through pipelines: the condition grammar (for if), ${...} interpolation (in URLs), and variables (captured with as). They're easy to confuse, so this section pins down each.

Conditions (if)

A condition is a small, checked grammar — not JavaScript. It's parsed at config time, so a malformed condition is rejected when you store the spec, not at runtime.

  • Builtins (about the in-flight message): status, ok, method, mime, isJson, isText, isBinary, name, isDirectory.
  • header("x-thing") — read a header.
  • Variables: $var.path.to.field — read a captured variable.
  • Literals: 'strings', numbers, true/false/null.
  • Operators: == != < > <= >=, && || !, parentheses. Numeric equality is by value.
(header("x-mode") == 'manage') && status < 300 && $order.total > 100

A condition that doesn't parse is a config-time error — you can't deploy a broken if.

${...} interpolation (URLs)

Interpolation fills placeholders in a call URL. Resolution order:

  1. captured variables,
  2. the input body's JSON fields,
  3. query params.

Dot-paths are allowed (${order.customerId}). An unresolvable placeholder is a 400 — interpolation never yields a silent empty string.

GET /data/customers/${order.customerId}

Note the difference from conditions: interpolation uses ${...} and is about building a string; conditions are an expression language about deciding true/false.

The URL plane: ${url.…}

Alongside the data plane above, a call URL can reference the incoming request's path and query through the reserved url root — this is how a pipeline forwards the addressed resource (transparent wrapping). The segment list is the peeled sub-path: the part of the request path beyond the matched spec prefix (for a .root spec, the whole sub-path), mirroring the positional params the query and template services expose.

Selector Resolves to
${url.path[0]} first peeled segment; ${url.path[-1]} the last
${url.path[1:]} / ${url.path[1:3]} a slice, /-joined (Python-style, end-exclusive)
${url.path} the whole peeled path, /-joined
${url.base[0]} / ${url.full} the mount-prefix segments / base+path joined
${url.name} the last segment (resource name)
${url.query.id} / ${url.query} one query param / the whole query string
${url.rest} exact remainder after the mount, including its leading and trailing slash

A selector that resolves to nothing is a 400, unless you mark it optional with ? (it elides, collapsing a neighbouring /, so a/${url.path[5]?}/ba/b) or give a default with || (${url.query.page || '1'}). Patterns are checked when the spec is stored, so a malformed ${url.path[} is rejected at author time, not at run time.

${url.rest} is the transparent-proxy form: unlike segment-joined ${url.path}, it preserves the addressed path byte for byte. The canonical passthrough wrapper can therefore be one line:

[ "GET /data/users${url.rest}" ]

Anything that isn't the url root is a data-plane dot-path as before (${order.customerId}); a data field literally named url is shadowed by the URL root.

Variables (as)

as: "$var" captures a step's result into a named variable (7.3) without disturbing the in-flight message. Captured variables are then available to:

  • later conditions ($order.status == 'open'),
  • later interpolation (${order.id}), and
  • transforms as JSONata variables ($order; see 7.6).

A failed captured call lands as {"_errorStatus", "_errorMessage"} in the variable, and the pipeline keeps going — so you can capture an optional lookup and branch on whether it succeeded.

Special variables

Inside transforms you also have runtime-provided values about the current message: $_status, $_ok, $_headers, and $_rawBody (and the body itself is the JSONata input $). $_user / $principal describes the original caller as {email, roles, …extra JWT claims}; it is unbound for anonymous callers. $_url describes the triggering URL (path segments and query). These let a pipeline mediate access without losing who called it.

Keeping them straight

Surface Syntax Question it answers
Condition (if) status < 300, $v.x == 'y' Should this step run?
Interpolation (URL/header) ${order.id} What URL or header value do I send?
Transform (body) $order.id, $sum(...) What's the new body?

Next: transforms in depth.


Previous: 7.4 Modes · Manual home · Next: 7.6 Transforms with JSONata →