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

# Dated property queries

> A compact query language for filtering employees and other entities by their dated property values

The dated property query language is the syntax used in the Employee list filter in Backoffice, the system-data employee browser, the system-integration sync-preview filter, and the MCP `list_employees` tool. A query compiles into a filter that runs against an entity's [dated properties](/platform/data-model#dated-properties), so the same data model that powers the rest of Twine also drives this filter syntax.

A query like the one below finds currently employed employees whose current salary is above 50,000:

```
employment_terminated = false AND salary_amount > 50000
```

## A single clause

The smallest unit of a query is a clause:

```
first_name = "John"
```

A clause is `<property> <operator> <value>`. The property name is the same name used elsewhere in Twine for that field - see [Employee](/platform/data-model/employee) for the full list of properties on the most common entity. When no operator is written, `=` is assumed.

## Comparison operators

| Operator          | Meaning                      |
| ----------------- | ---------------------------- |
| `=`               | equal                        |
| `!=`              | not equal                    |
| `>` `>=` `<` `<=` | ordering (numbers, dates)    |
| `~=`              | case-insensitive regex match |
| `in`              | list membership              |

Equality and inequality (`=`, `!=`) are type-tolerant: `employee_no = 123` matches a stored `"123"` and vice versa. This is intentional, because property values arrive from many different source systems and can land in storage with varying types.

## Value types

The parser recognises the following value forms:

| Form           | Example                   | Parsed as               |
| -------------- | ------------------------- | ----------------------- |
| Quoted string  | `"John"`, `"München"`     | string                  |
| Integer        | `50000`                   | integer                 |
| Decimal        | `2.5`, `2,5`              | float                   |
| Boolean        | `true`, `false`           | boolean                 |
| Date           | `2024-01-01`              | date                    |
| Blank          | `blank`, `nil`, `null`    | the "no value" sentinel |
| List           | `["a", "b"]`, `[1, 2, 3]` | list (only with `in`)   |
| Any other text | `pending`                 | string (naked)          |

### Strings

Quoted strings (`"John"`) are the canonical form. Use quotes whenever the value could be confused with another type, contains a comma, or begins or ends with whitespace.

Naked strings are the fallback: anything in value position that does not match a typed form is captured as a string up to the next comma. `note = hej baberiba` parses cleanly, with the value `hej baberiba`.

<Warning>
  A typo in a value silently degrades to a naked string. `salary_amount > 50O00` (with a letter O, not zero) parses as the string `"50O00"` and will never match a numeric salary. When a query is not returning what you expect, check that each value parsed as the type intended.
</Warning>

Quoted strings have no escape syntax. The first `"` after the opening quote ends the string.

### Numbers

Integers are bare digits. Floats accept both `.` and `,` as the decimal separator. Negative integers cannot be written as bare digits - use a float (`-5.0`) or a quoted string (`"-5"`).

### Booleans

`true` and `false`, lowercase only. Anything else (`True`, `1`, `yes`) is treated as a string.

### Dates

The canonical date form is `YYYY-MM-DD`. The separator can be a space or omitted entirely, so `2024 01 01` and `20240101` are also accepted. Invalid calendar dates such as `2024-02-30` cause the parse to fail.

### Blank

`blank`, `nil`, and `null` are equivalent and all parse to the same "no value" sentinel. `field = blank` matches when the property is missing from the entity entirely, when its value is `null`, or when its value is an empty list. `field != blank` is the negation. Other operators against `blank` (such as `>`) are not meaningful and return no match.

### Lists

Bracketed, comma-separated, and used only with `in`:

```
employee_no in ["EMP001", "EMP002", "EMP003"]
```

Strings inside a list must be quoted. Mixed types are allowed (`[1, 2.5]` works).

## Filtering by when a value was valid

Each clause runs against an entity's [dated property](/platform/data-model#dated-properties) timeline. By default it inspects only the **currently effective** entry. An optional `, <date constraint>` after the value changes which entries the comparison runs against.

There are four modes:

| Suffix            | Meaning                                                                                                     |
| ----------------- | ----------------------------------------------------------------------------------------------------------- |
| (none)            | The clause is checked against the entry effective today.                                                    |
| `, 2024-01-01`    | The clause is checked against the entry effective on 2024-01-01.                                            |
| `, >= 2024-01-01` | The clause matches if any entry whose `valid_from` satisfies the comparison also satisfies the value check. |
| `, *`             | The clause matches if any entry in the timeline (current or historical) satisfies the value check.          |

Worked salary-history examples:

```
salary_amount > 50000                    # current salary is above 50000
salary_amount > 50000, 2022-06-01        # salary effective on 2022-06-01 was above 50000
salary_amount > 50000, *                 # the timeline contains any salary above 50000
salary_amount > 50000, >= 2024-01-01     # a salary entry starting on or after 2024-01-01 is above 50000
```

<Warning>
  `, 2024-01-01` (no operator) selects the **value effective on that date**, which is the most recent entry whose `valid_from` is on or before the date. This is different from `, <= 2024-01-01`, which matches if any historical entry's `valid_from` is on or before the date. The first picks one entry to compare against; the second considers many.
</Warning>

## Combining clauses

Clauses can be combined with `AND` and `OR`. Both keywords are uppercase only - lowercase `and` is captured as part of a naked string. `AND` binds tighter than `OR`, so:

```
first_name = "John" OR first_name = "Jane" AND salary_amount > 50000
```

parses as `first_name = "John" OR (first_name = "Jane" AND salary_amount > 50000)`. Parentheses override precedence:

```
(first_name = "John" OR first_name = "Jane") AND salary_amount > 50000
```

## Worked examples

```
first_name = "John"
```

Employees whose current first name is John.

```
employee_no in ["EMP001", "EMP002"]
```

Employees matching one of the given employee numbers.

```
employment_terminated = false
```

Employees who are currently employed. The value is the boolean `false`, not the string `"false"`.

```
employee_no = blank
```

Employees with no employee number set.

```
employee_no != blank
```

Employees who do have an employee number.

```
salary_amount > 50000, *
```

Employees whose salary history contains any value above 50,000.

```
salary_amount > 50000, 2024-01-01
```

Employees whose salary effective on 2024-01-01 was above 50,000.

```
first_name ~= "^Jo..$"
```

Employees whose first name matches the case-insensitive regular expression - for example, `John` and `Joel` match, `Joke` matches, `joak` does not (the pattern requires exactly four characters).

```
(first_name = "John" OR first_name = "Jane") AND salary_amount > 50000
```

Employees named John or Jane whose current salary is above 50,000.

## Where this query language is used

The same syntax is accepted in every place that filters dated-property-backed entities:

* The Employee list filter in [Backoffice](/backoffice/index).
* The Backoffice system-data employee browser.
* The system-integration sync-preview filter.
* The `query` argument on the MCP `list_employees` tool.

## Limitations and gotchas

* **No `NOT` keyword.** Negation is expressed at the operator level with `!=` or `!= blank`. A negative lookahead can be used inside a `~=` regex when needed.
* **`AND` and `OR` are case-sensitive.** Lowercase `and` and `or` are not recognised and are captured as part of the surrounding value.
* **Quoted strings have no escape syntax.** A literal `"` cannot appear inside a quoted string.
* **Typos in numeric or date values degrade to strings.** The query still parses, but never matches the property's real value.
* **Negative integers must be written as a float or as a quoted string.** Bare negatives such as `age = -5` do not parse.
* **Property names accept ASCII plus the Latin-1 supplement** (`Ä`, `Å`, `Ö`, `ø`, `ñ`, and so on). Other Unicode scripts cannot be used as property names, though they are valid inside quoted string values.
* **Invalid calendar dates fail the parse** rather than silently degrading - `2024-02-30` returns an error.
* **The `id` field of a dated property is not addressable** from this language. It exists in the data model (see [properties with multiple simultaneous values](/platform/data-model#properties-with-multiple-simultaneous-values)) but the text query syntax always considers every `id` for a given key.
