If
The two-way branch: an expression over the upstream payload routes down the true or false port, payload unchanged. Expression syntax and wiring both ports.
The two-way branch: a condition over the upstream payload routes execution down the true or false port. The other branch's steps are skipped.
What it does
If evaluates one expression against the immediate upstream step's
output and picks a port. It is a transparent router: it never
changes the payload, it only decides where the payload goes.
Truthiness follows the usual rule that anything other than nil
and false counts as true, so payload["tags"] on its own fires
the true port whenever the list exists, and you don't have to coerce
the result to a literal boolean.
The steps on the branch that didn't fire are skipped, and the skip propagates: a merge further down never waits on a branch that didn't run.
The expression is checked when the step is saved, not only when a run reaches it. A parse error or a call outside the sandbox shows up on the step in the Inspector and in the activation guard, so a typo can't ship.
Config
One expression; truthy fires true, falsy fires false.
- Condition: the expression, with
payloadbound to the immediate upstream step's output. Required. Defaultpayload["amount"] > 1000.
payload["amount"] > 1000
payload["status"] == "active" and payload["score"] >= 50
String.contains?(payload["email"] || "", "@example.com")
Bracket access (payload["field"]), comparison, boolean and
arithmetic operators, in, string concatenation with <>, the
pipe, and a small allowlist of helpers (String.*, Map.*,
Enum.* without function arguments, guards like is_nil) are
available. It's a small expression sandbox, not a general
programming language: no anonymous functions, no assignment, no
case, and no module outside the allowlist.
Two equality operators: == is loose across the JSON boundary, so
a real boolean true from an AI step matches
"true" and the number 5 matches "5". === is strict.
Output
The upstream payload passes through unchanged on whichever port fired. If doesn't transform data, it only routes it. Given
{ "amount": 2500, "customer": { "email": "a@example.com" } }
and the condition payload["amount"] > 1000, the step on the
true port receives exactly that map and can keep reading
{{ payload.customer.email }}; whatever is wired to the false port
sees nothing at all.
Patterns
- Threshold gate: big orders to an approval, small ones straight through.
- Found check: after a get from store,
branch on
payload["found"]. - Error lane: a step with on-failure
continueemits{ "ok": false, "error": … }out its error port; an if on that lane can separate retryable failures from the rest before you notify anyone. See error handling. - Skip quiet periods: after a schedule, branch on a count from the previous step so the rest of the run only happens when there's something to do.
Gotchas
- Wire both ports. An unwired false branch silently drops every non-matching payload. The run just ends there.
- The condition reads the immediate upstream output only. Pull earlier values forward with set fields first if the decision needs them.
- A field that's missing reads as
nil, andnil > 1000raises, which fails the step. Guard with(payload["amount"] || 0)when the key is optional. - Per-item filtering of a list is reshape's filter task. If is a single yes/no on the whole payload.
Related
- Switch for more than two routes.
- Merge to rejoin the branches.
- Error handling for the error port.