Skip to main content

Control Flow nodes

All three nodes here are compiled specially: their own emit() functions are defensive stubs that always throw — the real compilation happens inside the shared exec-chain walker, since a fork into independent downstream sub-chains can't be expressed as a flat {imports, setup, body} fragment the way every other node type's output can.

Usable both on the main canvas and inside a Function Graph.

Branch — controlFlow.branch

An "if": evaluates "Condition" and continues down either "True" or "False", never both.

  • Inputs: in (exec) — "In"; condition (value) — "Condition"
  • Outputs: true (exec) — "True"; false (exec) — "False"
  • Config fields: none — the Condition literal, when unwired, is typed directly on the pin.
  • Constraints: at least one of True/False must have an outgoing wire (a Branch wired to nothing is almost certainly a mistake); each of True/False may have at most one outgoing connection.

The exact shape of the generated if depends on which arms are wired:

// Both True and False wired
if (n % 2 === 0) {
// ...True arm
} else {
// ...False arm
}

// Only True wired — no else; a false condition falls through with no effect
if (n % 2 === 0) {
// ...True arm
}

// Only False wired — the condition is inverted, not left as an empty positive branch
if (!(n % 2 === 0)) {
// ...False arm
}

A value computed inside one arm is scoped to that arm's own { } block — a sibling arm or a downstream node outside the Branch can't read it directly, exactly like hand-written JavaScript. See the Function Graph & Blueprint Mode example for the usual workaround (write the result to a variable inside each arm, then read the variable back out afterward) and How Code Generation Works for why.

Switch — controlFlow.switch

Routes execution to the output matching "Selection", or "Default" if nothing matches. Despite an internal "Switch on Int" label, Selection accepts any primitive type — number, string, or boolean.

  • Inputs: in (exec) — "In"; selection (value) — "Selection", any type
  • Outputs: default (exec) — "Default", plus one dynamic case-<id> exec output per entry in the node's Cases list (edited in the side panel — add/remove/edit case values there, not by adding pins on canvas)
  • Config fields: none — case values live in the Cases list; Selection's unwired literal is typed directly on the pin.
  • Constraints: case values must be unique; at least one case's or Default's output must be wired; Default may have at most one outgoing connection.
switch (status) {
case 0: {
// ...case 0's arm
break;
}
case 1: {
// ...case 1's arm
break;
}
default: {
// ...Default arm
}
}

Each case is wrapped in its own explicit { } block, since JS switch cases otherwise share one block scope — this keeps each arm's locally-declared variables from leaking into the next case, the same scoping guarantee Branch's arms get from if/else. A case left unwired on canvas simply gets no clause at all in the generated switch; a wired Default still catches any selection that would have matched it.

Sequence — controlFlow.sequence

Unlike Branch/Switch, which each pick exactly one output to run per request, Sequence runs every wired output, unconditionally, in left-to-right pin order. Use it whenever the relative order of two or more independent pieces of logic matters, but nothing about how they're wired otherwise forces that order.

  • Inputs: in (exec) — "In"
  • Outputs: one static then-0 (exec) — "Then 0" — plus any number of additional dynamic then-<n> exec outputs, added via a "+ Add pin" button rendered directly on the node face itself (not in the side panel — the same on-canvas affordance the variadic boolean operators use). Each pin has its own "×" button to remove just that pin and its wire.
  • Config fields: none.
  • Constraints: none of the "Then" pins are required to be wired — an unwired pin is simply skipped, with no clause emitted for it. Removing a pin never renumbers or re-targets a later pin's existing wire; a removed slot is never reused.

A pin left unwired contributes nothing to the output; every wired pin's arm compiles into its own { } block and all of them run, in pin order, on every request that reaches the Sequence node:

// Then 0 wired to a Branch, Then 1 wired to another Branch, Then 2 unwired
{
// ...Then 0 arm
}
{
// ...Then 1 arm
}

The canonical use case is a chain of independent early-return guard clauses, where the order the checks run in matters even though each check is otherwise self-contained:

function getDiscount(customer) {
if (!customer.isActive) return 0; // Then 0
if (customer.isEmployee) return 30; // Then 1
if (customer.isPremium) return 20; // Then 2
if (customer.totalSpent >= 1000) return 10; // Then 3
return 5; // Then 4
}

Each guard clause here is its own Branch (or other exec chain) wired into a separate Sequence pin, in the order the checks should run.

Like Branch/Switch, each wired pin's arm gets its own scope — a value computed inside one pin's chain can't be read from a different pin's chain. Share a value across pins by wiring the same producer node into every pin that needs it, exactly like the workaround described for Branch in the Function Graph & Blueprint Mode example.

Usable both on the main canvas and inside a Function Graph.