> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twine.se/llms.txt
> Use this file to discover all available pages before exploring further.

# Expressions

> Write a decision or a calculation as one line of text instead of an inner graph

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:

```
any(absences where twine_time_type in ["sick_leave", "child_sick_leave"] and overlaps(it, period))
```

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.

```
if salary_amount > 40000 then "high" else "standard"
```

Yields one of two branch names for a Flow Switch, reading `salary_amount` from the switch's
subject (the employee).

```
employee.salary_amount * worked_days / days(period)
```

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-engine/flow-control/flow_switch),
  [Flow Filter](/flow-engine/transformation/flow_filter) and
  [Flow Transform](/flow-engine/transformation/flow_transform) have a **Decide by** setting,
  *Rule* or *Inner engine*; [Flow Guard](/flow-engine/flow-control/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](/data-engine/transformation/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 `@`:

| Rule                             | Reads                                             |
| -------------------------------- | ------------------------------------------------- |
| `salary_amount @ 2026-01-01`     | the value effective on that date                  |
| `salary_amount @ period.from`    | the value effective on a date another value holds |
| `salary_amount @ latest`         | the newest entry, even one dated in the future    |
| `salary_amount @ latest_defined` | the newest entry that has a value                 |
| `salary_amount @ oldest`         | the first entry                                   |
| `salary_amount @ all`            | every value, newest first, as a list              |

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:

```
if any(absences where twine_time_type in ["sick_leave", "child_sick_leave"] and overlaps(it, period))
then "forfeit" else "sync"
```

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.
