FlowPilot has three small evaluation systems: conditions (used by dynamic values), property values (static or conditional), and expressions (the {{ }} interpolation and the assign action). Each is deliberately limited. This page documents exactly what is supported so you do not write something the runtime silently drops.
Conditions
A Condition is a pure, read-only test against a variable. It never mutates state and must be deterministic. Conditions read variables only, through a VarRef:
Each operator names a left (a VarRef) and, for most operators, a right literal.
Operators by type
| Type | Operators |
|---|
| String | equals, not_equals, contains, not_contains, starts_with, ends_with, is_empty, is_not_empty |
| Number | equals, not_equals, greater_than, less_than, greater_than_or_equals, less_than_or_equals |
| Boolean | equals, not_equals (shown as “is ON” / “is OFF” in the editor) |
| List | contains, not_contains, is_empty, is_not_empty (operates on array elements) |
The empty-check operators (is_empty, is_not_empty) take no right operand.
Logical operators
Compose conditions with:
| Operator | Shape | Notes |
|---|
and | { op: "and", conditions: Condition[] } | All must hold. |
or | { op: "or", conditions: Condition[] } | Any must hold. |
not | { op: "not", condition: Condition } | Negate. |
Shape examples
{ "op": "equals", "left": { "var": "user_type" }, "right": "premium" }
{
"op": "and",
"conditions": [
{ "op": "greater_than_or_equals", "left": { "var": "age" }, "right": 18 },
{ "op": "equals", "left": { "var": "agreed" }, "right": true }
]
}
Evaluation notes
- A type mismatch fails the comparison rather than throwing. A numeric operator on a non-number returns
false. A starts_with/ends_with/contains on a non-string returns false (for not_contains, an absent/non-string value returns true).
- A missing variable is treated as empty:
is_empty is true, is_not_empty is false.
- An unknown operator fails safely to
false.
- Both the dashboard renderer (
variable-runtime.ts) and the iOS SDK (ConditionEvaluator) implement the same operator set.
Property values
Component props can be a plain value or a PropertyValue<T>:
type PropertyValue<T> =
| { type: "static"; value: T }
| { type: "conditional"; cases: ConditionalCase<T>[]; else?: PropertyValue<T> };
Resolution rules:
static: return the value directly.
conditional: evaluate cases in order. The first case whose when condition is true wins.
- If no case matches, fall back to
else.
- If nothing matches and there is no
else, the result is undefined (the renderer uses its own default).
A ConditionalCase<T> is { when: Condition; value: PropertyValue<T>; label?: string }. Case values can themselves be conditional, so they nest.
Example: a color that depends on a variable.
{
"type": "conditional",
"cases": [
{ "when": { "op": "equals", "left": { "var": "user_type" }, "right": "premium" },
"value": { "type": "static", "value": "#FFD700" } }
],
"else": { "type": "static", "value": "#808080" }
}
Interpolation: {{ }}
String props support {{variableName}} interpolation. The braces are replaced with the variable’s current value, converted to a string.
- Only the variable’s value is substituted. The braces hold a bare variable name, nothing else.
- A missing variable resolves to an empty string.
- Interpolation re-runs when the variable changes.
{{ }} interpolation does string substitution only. There is no ternary or conditional syntax inside {{ }}. To switch a value based on a condition, use a conditional property value (case/else against a condition) instead.
To switch a value on a condition, use a conditional PropertyValue (shown above), not an expression inside {{ }}.
Expressions (the assign subset)
The assign action and node use a FlowExpression: a string in a small “JS-like” safe subset. The iOS SDK’s ExpressionEvaluator defines the supported contract:
Supported
- Literals:
true, false, null, numbers (decimal, negative, leading dot), and quoted strings ("..." or '...').
- Variable references: a bare identifier (
userName) or a vars. prefix (vars.userName).
- A single binary arithmetic operator (
+, -, *, /, %) when both operands resolve to numbers.
- String concatenation with
+ when either operand resolves to a string (the other operand is coerced).
Not supported
- Parentheses.
- Comparison operators (
>, <, ==, and so on). Use a condition for comparisons.
- Function calls.
- Multi-operator chains (for example
a + b + c). Only one top-level operator is parsed.
Failure handling
An expression that cannot be evaluated returns nothing, and the runtime treats that as “skip this assignment”. The variable is left unchanged. A misconfigured expression never writes an empty, zero, or false value by accident. Division or modulo by zero also skips. Use a condition and a setVariable action when you need comparisons or branching, since expressions cannot compare.
Examples
vars.goalWeight - vars.currentWeight // number subtraction
"Hi, " + vars.userName // string concat
vars.count + 1 // increment via expression
(vars.a + vars.b) * 2 // NOT supported: parentheses
vars.age > 30 // NOT supported: comparison, use a condition
Related pages