Logic Step
Route your workflow down different paths based on conditions you define.
The Logic step lets you branch your workflow based on conditions. You define a list of branches, each with its own condition. When the session reaches the Logic step, branches are evaluated in order—the first one whose condition is true determines which path the workflow takes next. If none match, the fallback branch is taken.
Branches
A Logic step contains an ordered list of branches. Each branch has a condition that is evaluated when the session reaches this step. Every branch must have a unique label.
| name | type | required | description |
|---|---|---|---|
label | string | yes | A unique name for the branch (e.g. "High Value Customer"). |
target | string | no | The step to route to when this branch matches (description or id). |
condition | object | yes | The rule that must be true for this branch to be taken. See Conditions below. |
Fallback branch
A catch-all branch is identified by label exactly Fallback branch. It is skipped during condition evaluation and used only when no other branch matches. Its condition must be an AND group with an empty conditions array:
{
"label": "Fallback branch",
"target": "default_step",
"condition": {
"type": "group",
"logicOperator": "AND",
"conditions": []
}
}Conditions
A condition can take one of three forms: a comparison between two values, an emptiness check on a single value, or a logical group that combines multiple conditions. All three forms are identified by a type field.
Persisted config uses two group levels: an outer AND or OR group whose inner groups use the opposite operator. Inner groups contain only comparisons and emptiness checks.
Comparison
A comparison condition evaluates a field from a previous step against a static value (or a list of static values) using one of the available operators.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be binaryRule. |
binaryOperator | string | yes | The comparison to apply. See Operators. |
leftOperand | string | yes | A mapping placeholder referencing a field from a previous step (e.g. {{Webhook.response.statusCode}}). |
rightOperand | string | array | yes | A static value (e.g. "200") or an array of strings for set equality / contains. |
The right operand must be a static value or an array of static strings. Comparing two mapping placeholders against each other is not currently supported.
Emptiness check
An emptiness check tests whether a single value is present or absent. The operand is typically a mapping placeholder. null and missing values are treated as empty.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be unaryRule. |
unaryOperator | string | yes | IS_EMPTY or IS_NOT_EMPTY. |
operand | string | yes | The value to check. |
Grouped conditions
A group combines multiple conditions using AND or OR logic.
| name | type | required | description |
|---|---|---|---|
type | string | yes | Must be group. |
logicOperator | string | yes | AND (all must be true) or OR (at least one must be true). |
conditions | array | yes | Nested conditions. Empty only on the fallback branch. |
Operators
Comparison operators
| operator | applies to | description |
|---|---|---|
EQUAL | numeric, string, date, time, set | Left equals right. Numbers compare numerically ("2.0" equals "2"). Sets compare as order-independent exact matches. Datetimes compare by UTC instant. |
NOT_EQUAL | numeric, string, date, time, set | Left does not equal right. |
GREATER_THAN | numeric, date, time, datetime | Left is greater than right. Missing operands evaluate to false. |
LESS_THAN | numeric, date, time, datetime | Left is less than right. |
GREATER_THAN_OR_EQUAL | numeric, date, time, datetime | Left is greater than or equal to right. |
LESS_THAN_OR_EQUAL | numeric, date, time, datetime | Left is less than or equal to right. |
CONTAINS | string, set | String: left contains substring right. Set: left contains every value in right (extras on the left are allowed). |
NOT_CONTAINS | string, set | Inverse of CONTAINS. |
STARTS_WITH | string | Left starts with right. |
DOES_NOT_START_WITH | string | Left does not start with right. |
ENDS_WITH | string | Left ends with right. |
DOES_NOT_END_WITH | string | Left does not end with right. |
IS_LONGER_THAN | string vs numeric length | left.length > Number(right). Right is an integer length, not another string. |
IS_NOT_LONGER_THAN | string vs numeric length | left.length <= Number(right). |
IS_SHORTER_THAN | string vs numeric length | left.length < Number(right). |
IS_NOT_SHORTER_THAN | string vs numeric length | left.length >= Number(right). |
IS_LONGER_OR_EQUAL | string vs numeric length | left.length >= Number(right). |
IS_NOT_LONGER_OR_EQUAL | string vs numeric length | left.length < Number(right). |
IS_SHORTER_OR_EQUAL | string vs numeric length | left.length <= Number(right). |
IS_NOT_SHORTER_OR_EQUAL | string vs numeric length | left.length > Number(right). |
A few things to note:
- String operators (
CONTAINS,STARTS_WITH,ENDS_WITH, and their negations) operate on the string form of the operand. - Ordering operators (
GREATER_THANand family) require comparable operands: numeric, civil dateYYYY-MM-DD, civil timeHH:mm:ss, or datetime. Non-comparable values fail evaluation. - Length operators compare the character count of the left operand to a numeric right operand. They do not compare
left.lengthtoright.length. Example: left"hello"and right"5"→IS_LONGER_OR_EQUALis true. - When
rightOperandis an array,EQUAL/NOT_EQUAL/CONTAINS/NOT_CONTAINSuse set semantics. A scalar is treated as a one-element set ("A"equals["A"]). Other operators join the array with", "and fall back to string comparison.
Emptiness operators
| operator | description |
|---|---|
IS_EMPTY | Operand is null, undefined, or an empty string. |
IS_NOT_EMPTY | Operand is not null, undefined, or an empty string. |
Examples
Single condition
Let's look at the simplest possible Logic step—one branch that checks whether a user's status equals "active".
{
"branches": [
{
"label": "Active user",
"condition": {
"type": "group",
"logicOperator": "AND",
"conditions": [
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "binaryRule",
"binaryOperator": "EQUAL",
"leftOperand": "{{`User`.`status`}}",
"rightOperand": "active"
}
]
}
]
}
}
]
}Checking for an empty value
Here's a branch that checks whether an email field was left blank before continuing.
{
"branches": [
{
"label": "Check if email is empty",
"condition": {
"type": "group",
"logicOperator": "AND",
"conditions": [
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "unaryRule",
"unaryOperator": "IS_EMPTY",
"operand": "{{`User`.`email`}}"
}
]
}
]
}
}
]
}Set membership (rightOperand array)
rightOperand array)Use an array rightOperand with EQUAL (exact set) or CONTAINS (subset).
{
"branches": [
{
"label": "Selected both required tags",
"target": "tagged_flow",
"condition": {
"type": "group",
"logicOperator": "AND",
"conditions": [
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "binaryRule",
"binaryOperator": "CONTAINS",
"leftOperand": "{{`Form`.`tags`}}",
"rightOperand": ["vip", "priority"]
}
]
}
]
}
}
]
}Length and date comparison
IS_LONGER_THAN tests character count against a number. GREATER_THAN on dates uses civil-date / datetime ordering.
{
"branches": [
{
"label": "Long note after cutoff",
"target": "review_flow",
"condition": {
"type": "group",
"logicOperator": "AND",
"conditions": [
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "binaryRule",
"binaryOperator": "IS_LONGER_THAN",
"leftOperand": "{{`Form`.`notes`}}",
"rightOperand": "100"
}
]
},
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "binaryRule",
"binaryOperator": "GREATER_THAN",
"leftOperand": "{{`Form`.`submittedOn`}}",
"rightOperand": "2026-01-01"
}
]
}
]
}
}
]
}Multiple branches with nested logic
Building on those basics, here's a complete example with three branches. Each branch uses a different combination of operators and nested groups to route customers to the right flow.
{
"branches": [
{
"label": "High-value active customer",
"target": "premium_flow",
"condition": {
"type": "group",
"logicOperator": "AND",
"conditions": [
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "binaryRule",
"binaryOperator": "EQUAL",
"leftOperand": "{{`User`.`status`}}",
"rightOperand": "active"
}
]
},
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "binaryRule",
"binaryOperator": "GREATER_THAN_OR_EQUAL",
"leftOperand": "{{`User`.`credit`}}",
"rightOperand": "1000"
}
]
},
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "binaryRule",
"binaryOperator": "CONTAINS",
"leftOperand": "{{`User`.`kind`}}",
"rightOperand": "vip"
},
{
"type": "binaryRule",
"binaryOperator": "GREATER_THAN",
"leftOperand": "{{`User`.`credit`}}",
"rightOperand": "10000"
}
]
}
]
}
},
{
"label": "New customer with valid email",
"target": "onboarding_flow",
"condition": {
"type": "group",
"logicOperator": "AND",
"conditions": [
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "binaryRule",
"binaryOperator": "EQUAL",
"leftOperand": "{{`Customer`.`kind`}}",
"rightOperand": "new"
}
]
},
{
"type": "group",
"logicOperator": "OR",
"conditions": [
{
"type": "unaryRule",
"unaryOperator": "IS_NOT_EMPTY",
"operand": "{{`Customer`.`email`}}"
}
]
}
]
}
},
{
"label": "Fallback branch",
"target": "standard_flow",
"condition": {
"type": "group",
"logicOperator": "AND",
"conditions": []
}
}
]
}Updated 21 days ago
