A condition step evaluates context.variables and returns the next step ID. Conditions live in ConditionRegistry. Each one reads its parameters from step.config (or context.variables.stepConfig at runtime) and emits a ConditionResult with a passed flag, a nextStepId, and an optional branchPath label.
Built-in conditions
| Field | Type | Description |
|---|---|---|
| if_then | boolean branch | Evaluate one expression. Routes to |
| switch_case | multi-way branch | Pick a branch from a list of cases. Config: |
| check_field_value | comparator | Compare any context field against a literal. Config: |
| check_tag_exists | tag check | Test contact tags. Config: |
| check_engagement_score | score gate | Compare a lead/engagement score against a threshold. Config: |
| filter_contacts | filter gate | A pass/stop gate for the current contact. Same shape as |
| goto | redirect | Unconditional jump to another step. Useful for shared sub-flows. |
| always_execute | passthrough | Always passes. Effectively a no-op branch — handy for placeholder slots in template flows. |
| ab_test | experiment split | Random-weighted branch. Use for A/B testing two action paths. |
Operators
check_field_value, filter_contacts, and if_then (when used flat) all delegate to the shared validateValue utility. Operator names are normalized via mapOperatorToValidator. Common ones:
| Operator | Meaning |
|---|---|
eq / equals / == | Strict equality |
neq / not_equals / != | Inequality |
gt, gte, lt, lte | Numeric / date comparison |
contains | Substring or array-includes |
in | Value is in an array literal |
not_in | Value is not in an array literal |
regex | Field matches a regular expression |
is_empty / is_not_empty | Null/empty check |
between | Numeric range, value is [min, max] |
Field paths use dot notation. contact.tags, triggerResult.source, pageVisit.url all work.
Examples
if_then
{
"id": "branch-1",
"type": "condition",
"config": {
"conditionType": "if_then",
"field": "contact.country",
"operator": "eq",
"value": "US",
"thenStepId": "us-flow-step",
"elseStepId": "intl-flow-step"
}
}
switch_case
{
"id": "switch-plan",
"type": "condition",
"config": {
"conditionType": "switch_case",
"field": "contact.plan",
"cases": [
{ "value": "free", "flowId": "free-onboarding" },
{ "value": "pro", "flowId": "pro-onboarding" },
{ "value": "enterprise", "flowId": "enterprise-onboarding" }
],
"defaultFlowId": "default-onboarding"
}
}
check_engagement_score
{
"id": "score-gate",
"type": "condition",
"config": {
"conditionType": "check_engagement_score",
"scoreThreshold": 75,
"operator": "gte",
"timeframe": "30d",
"trueFlowId": "send-to-sales",
"falseFlowId": "stay-in-nurture"
}
}
filter_contacts
{
"id": "must-have-email",
"type": "condition",
"config": {
"conditionType": "filter_contacts",
"field": "contact.email",
"operator": "is_not_empty",
"continueFlowId": "send-step",
"stopFlowId": null
}
}
stopFlowId: null ends the flow when the filter rejects.
DynamicQuery-backed segmentation
The filter_contacts condition handles single-field gates. For complex audience segmentation (multi-field AND/OR, nested conditions, aggregations) call into the Dynamic Query module from a custom action — the same query engine powers CRM audience segments and rule-engine rules.
Conditions are pure — they read from context but never mutate the database. To act on the result of a comparison, branch into an action step.