Skip to content

Condition Node

The condition node branches execution based on a boolean expression evaluated against the current execution context. It is the only node type in the workflow engine that evaluates an expression directly — everything else routes on a plain value match (see Router Node).

See Workflow Overview for the node/edge envelope this fits into.

FieldTypeRequiredDescription
expressionstringYesThe boolean expression to evaluate against the current execution context. Not JavaScript — see the callout below for the real syntax.

That’s the entire config shape — one field. All of the complexity in this node type is in what expression is allowed to contain.

Critical: expression is expr-eval syntax, not JavaScript

Section titled “Critical: expression is expr-eval syntax, not JavaScript”

This is the single most common mistake when authoring a condition node. The expression is parsed and evaluated by expr-eval, a sandboxed expression evaluator — it is not eval(), not new Function(), and it does not understand real JavaScript syntax. It has no access to Node.js globals, require, the filesystem, or any runtime API — only the variables in the execution context are in scope.

Concretely, this means:

  • Use == and !=, not === / !==. expr-eval has no strict-equality operators at all — === is a syntax error.
  • Use the words and, or, notnot &&, ||, !. The symbolic logical operators do not exist in this grammar.
  • Variable names must be bare and flatuserAge, intent, orderCount. There is no property access: context.userAge or ctx.user.age are not valid, even though the underlying execution context is technically a nested object at runtime. Every value you want to branch on must already be a flat key in the context — typically something an earlier llm node wrote via its outputVar.
JavaScript (wrong)expr-eval (correct)
userAge === 18userAge == 18
status !== 'active'status != 'active'
isVip && orderCount > 3isVip and orderCount > 3
!isBlockednot isBlocked
context.userAge > 18userAge > 18

If you write real JavaScript syntax here, you’ll typically get a parse error at evaluation time (for ===, &&, etc.) or a silently-wrong result (property access simply won’t resolve the value you meant). Either way, the fix is the same: flatten the value into its own context variable first (with an llm node, or by referencing whatever earlier node already wrote it as a flat outputVar), then write the expression using only ==/!=/and/or/not against bare names.

A missing variable evaluates false, not an error

Section titled “A missing variable evaluates false, not an error”

Changed 2026-09-21. If an expression references a name the current execution context does not hold — because that turn simply didn’t carry the fact, or a caller omitted it — the node now evaluates false and takes the false edge. Previously this threw, which failed the entire run and gave the end user a generic “Sorry, I ran into a problem” reply for what is almost always an ordinary missing-value case.

false rather than true on purpose: every gate should be written so the guarded, outbound-calling branch is the true one, so an unanswerable condition takes the branch that does nothing.

The run’s context records which names were unresolved, under _conditionUnresolvedVariables, so “why did this take the false branch?” is answerable from workflowRuns(sessionId) { context } without digging through logs:

{ "_conditionResult": false, "_conditionUnresolvedVariables": ["jobPostingId"] }

This is deliberately narrow. Only an unresolved variable reference maps to false. A syntax error, a type error, or any other evaluation failure still throws — those are authoring bugs, and a silent false would bury them.

It also means you can write a gate against a value that may or may not be supplied — jobPostingId > 0 covers “absent”, 0, "0", "" and prose in one expression — which is the recommended shape for anything feeding an outbound write, including values arriving via runWorkflowTask’s variables. Note expr-eval has no defined() or ?? operator, so this is the only way to express “only proceed if I actually got this.”

A condition node’s outgoing edges are matched against the expression’s evaluated boolean result, converted to the lowercase strings 'true' and 'false'. Both outgoing edges are required, and their label must be exactly 'true' or 'false' — not 'True', not 'yes', not omitted. If the evaluated result has no matching edge, the workflow now throws a clear error at execution time (an earlier engine version stopped advancing silently instead — that failure mode no longer exists, but a mislabeled edge will still stop your workflow at that node).

A generic support-ticket workflow decides whether an issue can be auto-resolved based on a priority score an earlier llm node extracted into priorityScore (a flat numeric context variable):

{
"nodes": [
{
"id": "score_priority",
"type": "llm",
"label": "Score Ticket Priority",
"config": {
"outputVar": "priorityScore",
"promptTemplate": "Rate the urgency of this support message from 1-10, respond with ONLY the number: \"{{userMessage}}\"",
"extractFirstInteger": true,
"streamOutput": false
},
"position": { "x": 0, "y": 0 }
},
{
"id": "check_urgent",
"type": "condition",
"label": "Is This Urgent?",
"config": { "expression": "priorityScore >= 7" },
"position": { "x": 0, "y": 100 }
},
{
"id": "escalate",
"type": "response",
"label": "Escalate to Human",
"config": {
"messageTemplate": "This looks urgent — I'm connecting you with a support agent right away.",
"mood": "concerned"
},
"position": { "x": -150, "y": 200 }
},
{
"id": "auto_handle",
"type": "response",
"label": "Continue with Bot",
"config": {
"messageTemplate": "Thanks for the details — let's see what I can do to help.",
"mood": "helpful"
},
"position": { "x": 150, "y": 200 }
}
],
"edges": [
{ "id": "e1", "source": "score_priority", "target": "check_urgent" },
{
"id": "e2",
"source": "check_urgent",
"target": "escalate",
"label": "true"
},
{
"id": "e3",
"source": "check_urgent",
"target": "auto_handle",
"label": "false"
}
]
}

priorityScore >= 7 is valid expr-eval syntax — a bare variable name, a comparison operator, a number literal. Note >= itself is fine (comparison operators are shared with JavaScript); it’s equality, logical combinators, and property access where the two languages diverge.

A slightly more complex expression combining two flat context variables:

{ "expression": "priorityScore >= 7 and isRepeatCustomer == 1" }

(isRepeatCustomer here would need to have been written earlier as a flat 0/1 — or any other expr-eval-comparable scalar — by another node, not as a nested object property.)

  • Router Node — the multi-way alternative to condition, useful once you have more than two outcomes.
  • LLM Node — the usual source of the flat context variable a condition node evaluates.
  • Response Node — often sits on both branches of a condition.
  • Workflow Overview — full node/edge reference.
  • Best Practices — more templating and reliability gotchas.
  • Examples — complete end-to-end workflows.
  • Workflows API Reference — mutations for creating and publishing workflows.