This guide explains how to write rule formulas (Conditions + Actions) in the Flow Builder.
A rule has two logical parts:
- Condition (When): A boolean expression that decides whether the rule fires. Use * to match always.
- Action (Then): One or more assignment lines that compute and export variables downstream.
Syntax:
variable_name := expression
Rules:
- One assignment per line.
- Left-hand side (LHS) must be an identifier: [a-zA-Z_][a-zA-Z0-9_]*.
- Right-hand side (RHS) must not be empty and must start with a value (identifier, number, string, or ().
- Separate statements using a newline (preferred) or ;.
The variables that become available to downstream nodes follow this precedence order:
1. Explicit export line: export a, b, c (commas or spaces allowed)
2. Return line: return result_variable (optional sugar for a single-variable export)
3. Default: If neither export nor return is present, ONLY the last assignment’s variable is exported.
Rationale: Keeping the default narrow prevents accidentally leaking many temporaries and keeps dependency graphs simpler. Use export to intentionally expose multiple values.
Examples: ``` # Implicit export (no export/return) => ONLY last variable exported base := usage_minutes * unit_price price := base * discount_factor # only price exported
base := usage_minutes * unit_price raw := base * discount_factor final := raw * tax_rate export base, final # exports base + final
subtotal := usage_minutes * unit_price adjusted := subtotal * 0.95 return adjusted # only ‘adjusted’ exported ```
Start a line with # to add a comment. Comments are ignored by validation and export logic.
# Apply base rate
price := usage_minutes * unit_price
Use single or double quotes:
err_not_found := "Not found"
err_localized := 'No encontrado'
Contents of strings are ignored for unknown-variable detection, so words inside quotes won’t trigger errors.
Conditions can use:
- Comparison operators: == != > < >= <=
- Logical operators: AND OR NOT
- Parentheses for grouping
- Identifiers, numeric literals, and string literals
Conditions must NOT contain assignments (:=).
Example:
(usage_minutes > 100 AND customer_role == 'premium') OR region == "EU"
| Error Message | Meaning | Fix |
|—————|———|—–|
| Assignment not allowed in condition | Found := in condition section | Remove assignment or move to Action |
| Only one assignment allowed per line | Multiple := in one line | Split into separate lines |
| Right-hand side required | Missing RHS after := | Add an expression |
| Right-hand side must start with value or ( not operator | RHS begins with operator like + or = | Prepend a value or wrap in parentheses |
| Right-hand side must contain a value | Empty or invalid RHS | Add a literal / variable |
| Unbalanced parentheses | Parenthesis mismatch | Balance ( and ) |
| Unknown: var1, var2 | Referenced variables not yet known | Export earlier or fix name |
Unknown variables list aggregates unresolved identifiers (excluding keywords, numbers, locals, and string contents).
snake_case for variable names (net_price, base_minutes).export when exposing more than one variable for clarity.# Multi-step with explicit export base := usage_minutes * unit_price scaled := base * region_multiplier final := scaled * tax_rate export final# Export multiple values subtotal := usage_minutes * unit_price vat := subtotal * vat_rate export subtotal, vat# Conditional guard (always true example) * # condition # action result := usage_minutes * unit_price
You can traverse arrays and aggregate values directly in formulas.
items[*].price # expands to project(items, "price") order.items[*].price # nested: expands to deep_project(order, "items", "price")Rules: - Always include[*]on the array segment. Shorthand without it is not supported. -items(or the nested segment) should be an array of hashes/objects. Missing fields become nil.Aggregation / List Functions (Collections – Phases 1 & 2)
All dual-mode (array or variadic where meaningful). Phase 2 adds transformation, conditional, and averaging helpers.
| Function | Description | Notes |
|---|---|---|
sum |
Sum of numeric / numeric-string values | Non-numeric ignored; empty => 0 |
count |
Count non-nil values | Works on array or variadic |
min |
Minimum numeric value | Returns nil if none |
max |
Maximum numeric value | Returns nil if none |
first |
First element of array | Wraps scalars automatically |
last |
Last element of array | |
take |
First N elements | Negative / zero => [] |
map |
Transform each element with an expression referencing item |
Errors yield nil in position |
filter |
Keep elements where predicate referencing item is truthy |
Invalid predicate => [] |
distinct |
Remove duplicate elements | Ruby equality semantics |
average / avg |
Mean of numeric / numeric-string values | Empty => nil |
sumif |
Sum items (or projection) where predicate true | Projection optional |
countif |
Count elements where predicate true | |
trace |
Pass-through value (instrumentation hook) | Currently no-op |
Numeric coercion: numeric-looking strings (e.g. “5”, “3.5”) are converted; others skipped.
Nested projection:
- Use dot notation with a single [*] at the array segment: order.items[*].price
- Internally, nested patterns are evaluated via deep_project(root, "path", "field") for robust traversal.
- Deeper chains are supported as repeated nested segments (e.g., a.b[*].c[*].d evaluates stepwise). Always place [*] on each array segment.
total := sum(items[*].price)
range_min := min(items[*].price); range_max := max(items[*].price)
head_two := take(items[*].price, 2)
smallest := min(["5", 2, "3.5"]) # => 2
largest := max(["5", 2, "3.5"]) # => 5.0
first_price := first(items[*].price)
sum([]) # => 0
min([]) # => nil
sum(["x", nil]) # => 0
count([nil, 1, 0]) # => 2 (nil excluded, 0 counts)
take(items[*].price, -2) # => []
Future (possible extensions): group_by, reduce, multi-level projection (items[*].addresses[*].city).
You can define array attributes directly on a Calculation through the UI by selecting the new Array type. The value accepts either:
- A JSON array (preferred): [ {"price": 2.5, "name": "A"}, {"price": 3.0, "name": "B"} ]
- A comma or newline separated list of scalars: red, green, blue → ["red", "green", "blue"]
Parsing rules:
- If the value starts with [ and ends with ], we attempt JSON.parse. If it yields an array, it’s used as-is (objects/hashes allowed).
- Otherwise, the raw string is split on commas or newlines, trimmed, and empty segments removed.
Usage inside formulas:
total := sum(items[*].price) # items is an array of hashes with a price key
first_name := first(users[*].name) # users is an array of hashes/objects
colors_count := count(colors) # colors is an array of strings
average_price := average(project(items, "price"))
Edge considerations:
- Malformed JSON falls back to the simple splitter (so [1, becomes ["[1"]). Use valid JSON for nested structures.
- Empty string value becomes an empty array [].
- Boolean / numeric coercion is not applied inside array elements; provide numbers directly if needed.
This enables richer default data sets for rules without requiring runtime parameters.
Temporary experimentation (Sept 2025) briefly exported all assigned variables by default. We reverted to: only the last assignment is exported unless an explicit export or return is present. If you authored formulas during that window expecting implicit multi-variable export, add an export line listing each variable you need downstream.
### No Implicit Default Variables
There are no built-in default variables like price or metadata automatically injected. Variables come from:
1. Calculation metadata keys (each key is exposed as its own variable)
2. Runtime parameters you pass at evaluation time
3. Variables exported from upstream rules
Note on deprecated templates: When the FEATURE_TEMPLATES flag is enabled, template schema defaults are merged first (lowest priority) and can seed context. By default this feature is disabled and templates are soft-hidden; prefer metadata.
The editor and validator recognize collection & aggregation functions (project, deep_project, sum, count, min, max, first, last, take, map, filter, distinct, average/avg, sumif, countif, trace). Using them will not generate unknown-variable warnings. If you see an unknown warning, check spelling and parentheses.
### Autocomplete & Function Hints
When typing inside the Action (formula) editor, a suggestion dropdown appears as soon as you begin a token. It includes:
- Inputs (calculation metadata & runtime inputs)
- Previously exported variables upstream
- Operators (contextual to condition vs formula)
- Functions (collection helpers)
Advanced behavior:
- Ranking favors exact matches, then prefix, then substring. Functions receive a mild boost for discoverability.
- Function insertion places the caret inside parentheses. For pattern functions (e.g. MAP, FILTER, SUMIF) a skeleton is provided (MAP(array, item.price * 1.0)). Replace array and expression as needed.
- Condition editor exposes a starter template when you begin typing if (inserts grouped logical pattern) to encourage explicit grouping with parentheses.
- Footer hints in the dropdown remind you: Tab/Enter accept, Esc closes, arrow keys navigate.
- Aliases are de‑duplicated: when you type a longer prefix (avera) only AVERAGE is shown (not AVG). Typing avg shows the alias entry.
- Inside an export line, function suggestions are suppressed (only variables/inputs make sense there).
Function suggestions display a short description plus a usage signature, e.g.:
SUM(array_or_values) # Adds numbers
FILTER(array, predicate) # Keeps elements where predicate true
PROJECT(array, "field") # Extract field from each element
Accept a suggestion with Tab or Enter. For functions, parentheses are inserted and the caret is placed inside. Example: typing ave and pressing Tab yields:
AVERAGE()
with the cursor ready for arguments. Aliases (e.g. AVG) appear as separate entries.
If the dropdown obstructs your view, press Escape to dismiss it temporarily.
Q: Do I need both return and export?
A: No. If an export line exists it wins; return is optional sugar for a single variable. Without either, the last assignment wins.
Q: Can I export a variable I didn’t assign? A: Exporting an unknown variable will surface an unknown error.
Q: Are variables case-sensitive?
A: Yes (total ≠ Total).
Last updated: 2025-09-11