> ## 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 a graph of nodes

Most business logic in an integration is one sentence: *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 one line of text
instead of a graph of nodes, and it reads the way a person would. Expressions work in any data
engine (a property mapping, a sync condition, an inner graph) and in flows. When a flow's
decision node evaluates an expression to choose what happens next, the node calls it a
**rule**; the language is the same everywhere.

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:

* **The [Expression](https://docs.twine.se/data-engine/transformation/expression.md) node**, which brings the language
  into any data engine: a property mapping, a sync condition, an inner graph.
* **Rule mode on the flow decision nodes.** [Flow Switch](https://docs.twine.se/flow-engine/flow-control/flow_switch.md),
  [Flow Filter](https://docs.twine.se/flow-engine/transformation/flow_filter.md) and
  [Flow Transform](https://docs.twine.se/flow-engine/transformation/flow_transform.md) have a **Decide by** setting,
  *Rule* or *Inner engine*; [Flow Guard](https://docs.twine.se/flow-engine/flow-control/flow_guard.md) 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.

## 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.

### When an entry starts

Every entry in a history starts on a date. `valid_from(x)` reads that date instead of the value,
and `entry_id(x)` reads the entry's id (the id that keeps, for example, two employments apart).
Put the `@` after the call to choose the entry:

| Rule | Reads |
| - | - |
| `valid_from(employment_terminated) @ latest` | the date the newest entry starts |
| `valid_from(salary_amount)` | the date the salary in effect today started |
| `entry_id(employment_start_date) @ all` | every entry's id, newest first |

An entry without a start date has held since forever, so `valid_from` gives nothing for it, and
nothing is neither before nor after any date. To keep syncing employees for two months after
they leave:

```
not employment_terminated @ latest
  or valid_from(employment_terminated) @ latest > add_months(today, -2)
```

## Comparing

`=`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, and `~=` (a case-insensitive pattern). `and`,
`or`, `not` combine them; parentheses group.

A `~=` pattern reads Swedish and other non-English letters as letters: `"måleri"` matches
"MÅLERI", and `.` stands for one letter, so `"m.leri"` matches "Måleri".

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.

A dotted path also works on a function's result:
`first(absences where twine_time_type = "sick_leave").start_date` reads the start date of the
first sick-leave absence. `first` and `last` keep the list's own order. On an empty list they
give nothing, and so does any field read on that nothing.

## 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)`.
* **History:** `valid_from(x)` and `entry_id(x)`, see [When an entry starts](#when-an-entry-starts).

## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.