All nodes/Logic & Flow/Branching

If / Else

Conditional branching: evaluates an expression and routes execution to the 'true' or 'false' branch. The expression supports comparisons and access to node data: {{nodes.llm_1.output.score}} > 0.8.

If / Else
InputTrue
False

Type in the graph: if

Exec

An error branch can be enabled (expose_error_output) to handle failures on their own path.

Ports can be split into separate execution and data handles.

Try it

Minimal working workflow

Entry
If / Else
Exit
  • Execute + Data
Press “Copy nodes”, open the editor and hit Ctrl+V on the canvas.

Runs as pasted

When to use it

Reach for this node when execution forks on data the graph already has: a score from a model, a field from a document, the length of a text, a flag from the database. There are exactly two branches. When you need more than two directions, use switch — a chain of five conditions reads worse than one list. When a step has to repeat while a condition holds, that is while_loop: a link from a branch back up the graph cannot be built, the scheduler treats such a graph as cyclic and refuses to run it.

How it works

The condition is a Jinja template, and it has two working shapes. A whole expression inside braces ({{ nodes.classify.output.score > 0.8 }}) is evaluated and returns a real boolean. The mixed shape ({{ nodes.classify.output.score }} > 0.8) substitutes the value first and then parses the resulting string as one comparison of two literals: >, >=, <, <=, ==, != and nothing else.

The node computes nothing on top of its input: the selected output carries exactly the value that arrived. Downstream templates can read {{ nodes.<id>.output }} — that value — and {{ nodes.<id>.branch }} — the name of the branch that fired, true or false.

The branch not taken is not a failure. Nodes on it end up skipped with the reason “An upstream branch went the other way”, and the run still finishes as completed: run status reports errors, not how many nodes ran.

Common mistakes

  • A compound condition in the mixed shape is always true. After substitution {{ a }} > 1 and {{ b }} < 2 becomes a string that is not a single comparison — and a non-empty string is truthy. Keep the logic inside the braces: {{ a > 1 and b < 2 }}.
  • Text comparison belongs inside the braces too. {{ inputs.input }} == 'yes' stops parsing as an expression after substitution and takes the true branch for any input. Write {{ inputs.input == 'yes' }} instead.
  • A typo in a path silently yields false. A missing path substitutes as empty, and a comparison with an empty operand is not performed. Check what the condition became: the run details show a Rendered templates block for the node, with the unresolved paths listed under the __undefined_refs__ key.
  • An empty condition will not deploy — the expression is required.
  • Both branches into one node is a normal shape. The engine runs that node once, with the payload of the branch that fired; the validator warns about it so you do not expect the values of both branches at the same time.

Inputs

PortWirePayloadNotes
InputinputExecute + Dataexecute_data

Outputs

PortWirePayloadNotes
TruetrueExecute + Dataexecute_data
FalsefalseExecute + Dataexecute_data

Configuration

FieldTypeDefaultDescription
Conditioncondition_expressionstring""

Expression that evaluates to truthy/falsy. Supports comparisons: {{nodes.llm_1.output.confidence}} > 0.8

supports templates

Shared fields

Every node has these three — the platform adds them, not the node author.

  • expose_error_output — When enabled, show an execution output to connect nodes that run if this step fails.
  • split_ports_in — Show separate execution and data input handles instead of one combined port.
  • split_ports_out — Show separate execution and data output handles instead of one combined port.