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 dynamiccase-<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 dynamicthen-<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.