Logicbranchstable

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.preferredLanguage set by a customScript that read the contact record
  • Implement business hours by reading a flag a previous customScript computed (vars.isOpen → true or false, 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-edge out handle, an unmatched branch logs branch with no matching case and no fallback edge and 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) (with null becoming ''); numeric ops then coerce back via Number(). This means vars.x === 0 matches value: "0" but also value: 0, and null is 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) and caller.<…> (caller.from, caller.to, caller.did). Anything else returns undefined and the comparison silently uses the empty string. Typos like vars.foo.bar.baz on 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 matches op 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 returns false and routing falls through to on.__next. The worker now logs a warn-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 for invalid regex in matches op if a matches case never fires.
  • The legacy varPath field is honored as an alias for path. Generators MUST emit path; saved flows from older versions of the editor may still use varPath and continue to work. Don't mix them in a single case.

See also