Branch on variable
Route to different next steps based on a value the flow already has — for example, the caller's number at flow start, or a result returned by a sub-flow.…
What it does
A pure case dispatcher. Each case reads a value from the call — a flow
variable (vars.x, or a field inside one, vars.customer.tier) or a fact
about the call (caller.from, caller.to, caller.did) — compares it with
the case's operator, and jumps to the first case that matches. Cases are
checked top to bottom.
The cases live at config.cases as an array of
{ path, op, value, nodeId } — the canonical shape shared by every
case-supporting node (see invariant I-3). op is one of ==, !=, >,
>=, <, <=, contains, startsWith, endsWith (those three ignore
case), in (value is an array), matches (a regular expression) or
exists (no value). value keeps its JSON type: 200 and "200"
compare equal, but true and "true" do not.
In the editor each case is a row — variable, operator in plain words, a typed value, and the step to go to — and each row has its own handle on the card, which reads like "API status code equals 200 → Read customer".
When no case matches, the call takes Otherwise: node.on.__next (the
walker also accepts on.default). The card's last row, "Otherwise", is that
exit. Always wire it; a branch with no match and no Otherwise leaves the
call with nowhere to go. For "set a value, then immediately route on it,"
use the Set variable node's built-in cases instead — it does the write
and the dispatch in one step. For digit menus, use IVR menu which has
the same case shape applied to gathered DTMF.
When to use it
- Route VIP callers based on their inbound number — "calls from area code 212 go straight to the priority queue"
- Branch on a value collected by an upstream Call an API step — e.g. send paid customers down one path and trial users down another
- Pick a language path based on
vars.preferredLanguageset by a customScript that read the contact record - Implement business hours by reading a flag a previous customScript
computed (
vars.isOpen→trueorfalse, picked as "Yes / no") and routing accordingly - After a sub-flow returns, branch on whatever the sub-flow stamped
into vars (e.g.
vars.lookupResult) without writing the value again
Configuration
Route to different next steps based on a value the flow already has — for example, the caller's number at flow start, or a result returned by a sub-flow. For a digit menu, use the IVR menu node's built-in cases. For 'set a value, then route on it', use the Set / update variable node's cases. Only reach for this when neither of those fits.
This node has no configurable fields.
Outgoing events: __next
Examples
Route VIPs by inbound area code
Inbound calls starting with +1212 (Manhattan) get the VIP path.
Anyone else falls through the right-edge out handle to the standard
queue.
{
"id": "vip-check",
"type": "branch",
"config": {
"cases": [
{ "path": "event.from", "op": "startsWith", "value": "+1212", "nodeId": "vip-queue" }
]
},
"on": { "__next": "standard-queue" }
}
Multi-case dispatch on a stored variable
A previous step wrote vars.tier. Each case targets a different
downstream node; the right-edge fallback catches anything not enumerated.
{
"id": "route-by-tier",
"type": "branch",
"config": {
"cases": [
{ "path": "vars.tier", "op": "==", "value": "gold", "nodeId": "gold-path" },
{ "path": "vars.tier", "op": "==", "value": "silver", "nodeId": "silver-path" },
{ "path": "vars.tier", "op": "matches", "value": "^(trial|free)$", "nodeId": "trial-path" }
]
},
"on": { "__next": "default-path" }
}
Gotchas
- No-match falls through to
on.__next, not to the first case. If you forget to wire the right-edgeouthandle, an unmatched branch logsbranch with no matching case and no fallback edgeand the flow stalls. Always wire a default. The literal'end'is a valid fallback when the right answer is "hang up." - All comparisons are stringified before the operator runs.
Both sides are coerced to strings via
String(value)(withnullbecoming''); numeric ops then coerce back viaNumber(). This meansvars.x === 0matchesvalue: "0"but alsovalue: 0, andnullis indistinguishable from"". For strict numeric comparisons stash the value as a string and use>,>=, etc. - A case path reads three roots.
vars.<dotted>(a flow variable or a field inside one),steps.<id>.ok/.status/.error(an earlier Call an API step's own result — pick it in the editor under the step's name: succeeded, status code, error) andcaller.<…>(caller.from,caller.to,caller.did). Anything else returnsundefinedand the comparison silently uses the empty string. Typos likevars.foo.bar.bazon a non-object intermediate also return undefined — the path walker tolerates it instead of throwing. - Cases are evaluated in array order; first match wins. If two cases would both match a value, only the first one fires. Order your cases from most-specific to most-general (e.g. exact-match before regex).
- Invalid regex patterns in the
matchesop evaluate to non-match. This is defensive — a typo in a pattern (unterminated group, bad escape, etc.) won't hang or crash the flow; the case simply returnsfalseand routing falls through toon.__next. The worker now logs awarn-level entry containing the bad pattern, the node id, and the underlying compile error, so authoring mistakes surface in logs instead of failing silently. Grep worker logs forinvalid regex in matches opif amatchescase never fires. - The legacy
varPathfield is honored as an alias forpath. Generators MUST emitpath; saved flows from older versions of the editor may still usevarPathand continue to work. Don't mix them in a single case.
