Skip to main content
Most decisions in a flow are one sentence of business logic: skip the variable salary if the employee had a sick day this month, treat anyone earning over 40 000 as senior, pro-rate the salary by the days worked. An expression lets you write that sentence as text, and the flow reads it the way a person would. When a decision node evaluates an expression to choose what happens next, the node calls it a rule; the language is the same either way. Three separate expressions, each a complete decision or calculation on its own:
True when at least one absence in the absences list is sick leave or care of a sick child and falls inside the month held in period. Use it on a Flow Guard to skip the variable-salary sync.
Yields one of two branch names for a Flow Switch, reading salary_amount from the switch’s subject (the employee).
Calculates a pro-rated salary for a Flow Transform: the salary times the days worked, divided by the number of days in the period. Expressions are available in two places, backed by the same language:
  • Rule mode on the decision nodes. Flow Switch, Flow Filter and Flow Transform have a Decide by setting, Rule or Inner engine; Flow Guard has a rule holds option under Bail when. In rule mode the node’s inner graph is kept but not used, so you can switch back without losing anything.
  • The Expression node, which brings the same language into any data engine: an inner graph, a sync condition, a property mapping.

Names

A rule reads values by name. A name resolves, in this order, to:
  1. it — the current item inside a where (see below).
  2. A field of that item, when inside a where.
  3. A wired port of the Expression node (input, a, b, c, d).
  4. An attribute of the subject — the node’s Subject assign, the guard’s Assign, the filter’s current item (which is also it in a filter rule), or the data engine’s record. salary_amount on an employee subject reads their salary.
  5. A flow assign — employee, absences, period, whatever earlier nodes wrote.
Dotted paths walk into a value: employee.salary_amount, period.from, it.start_date. A custom property has a quoted name: employee."Cost Center", or subject."Cost Center" when you are reading the subject directly. subject always means the subject; today is the date the rule runs on. A name the rule cannot find is an error, never a silent empty value, so a typo cannot pass as “no data”. A field that exists but holds nothing is nil, because missing data is normal.

Dated properties

An employee’s salary has a history. Reading salary_amount gives you the value effective today. To pick another moment, use @: These are the same moments the Assert, Any and Refute nodes offer, so a rule and a guard next to it mean the same thing by the same words.

Comparing

=, !=, <, <=, >, >=, in, not in, and ~= (a case-insensitive pattern). and, or, not combine them; parentheses group. Comparison is forgiving where source data is messy: 123 = "123", 1 = 1.0, "sick_leave" matches the stored code whether it arrived as text or a symbol, and a date compares with its 2026-01-01 text. x = blank is true for nothing, an empty text, or an empty list. Ordering against a missing value is simply false. Ordering a text against a number is an error. in takes a list (x in ["a", "b"]), a date range (it.start_date in period), or a text ("ab" in "cabin"). from .. to builds a date range from two dates.

Lists: where and it

absences where twine_time_type = "sick_leave" keeps the items of a list for which the condition holds. Inside the condition the item’s fields are in scope by their bare names, it is the item itself, and other assigns are still reachable. For a list of employees those bare names are the employee’s properties, the same names the property mapper shows: employees where salary_amount > 40000, employees where employment_terminated = false and "Cost Center" = "1200" (a custom property in quotes). A record’s base fields are reachable too, after its properties: id, active_status, manager_id. The @ selectors work on them as anywhere else: employees where salary_amount @ period.from > 40000. Functions summarise a list: count(...), any(...), all(...), sum(...), min(...), max(...), avg(...), first(...), last(...). A field read on a list reads it on every item, so sum(absences.minutes) adds up every day of every absence.

Choosing and calculating

if condition then value else value picks one of two values; chain with else if. A Flow Switch rule yields the name of the branch to take:
A rule that yields true or false picks the branch named so. Arithmetic uses +, -, *, /. A calculation on a missing value is missing; dividing by zero is an error.

Functions

The editor’s Names and functions panel under every rule field lists them all. The ones you will reach for most:
  • Dates: days_between(a, b), add_days(d, n), add_months(d, n), start_of_month(d), end_of_month(d), year(d), month(d), day(d), weekday(d), date(text).
  • Ranges: range(from, to), days(range), overlaps(a, b), within(x, range), days_in(x, range). A record with start and end dates (an absence, a schedule) counts as a range, so overlaps(it, period) works directly on an absence.
  • Text: trim, lower, upper, starts_with, ends_with, replace, matches, concat, to_string.
  • Numbers: round(n, digits), floor, ceil, abs, div, mod, number(text).
  • Fallbacks: coalesce(a, b, ...) gives the first value that is not blank; is_blank(x).

Errors

The editor checks a rule as you type and shows the problem with a caret under the offending text. A rule that does not compile still saves; it is listed in the Problems pane, and at run time the node treats it the way it treats a failing inner graph, according to its On inner error setting. Everything is logged; a rule never fails quietly.