Skip to main content
The dated property query language filters employees and other entities by their dated properties and by their mappings to system integrations. It is the same syntax everywhere a query is accepted, from the employee list filter to the “Load employees by query” flow step; see Where this query language is used. A query like the one below finds currently employed employees whose current salary is above 50,000:

A single clause

The smallest unit of a query is a clause:
A clause is <property> <operator> <value>. The property name is the same name used elsewhere in Twine for that field - see Employee for the full list of properties on the most common entity. The operator is required: first_name "John" is a parse error.

Comparison operators

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:

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. It ends at the next comma, before the next AND or OR, or at a ) that closes a group. note = hej baberiba parses cleanly, with the value hej baberiba, and status != inactive AND employee_no = "9550" is two clauses. To keep a standalone and or or inside a value, quote it: department = "Sales and Marketing". Parentheses inside a value are kept when they are balanced, so department = Sales (EU) works unquoted.
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.
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. A bare negative integer such as -5 is read as the text "-5": = and != still match a stored -5, but > and < compare it as text. Write it as a float (-5.0) to compare it as a number.

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:
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 timeline. An optional , <date constraint> after the value decides which entries the comparison runs against:
Without a suffix, a clause does not read the current value of a field with history. salary_amount > 50000 matches nothing when every salary entry has a start date. Add , today for the current value. It is safe on any field: an entry with no valid_from counts as effective since forever, so , today also matches it.
today is resolved each time the query runs, so a saved query stays relative. It is only a keyword in the suffix; as a value, first_name = today compares against the text today. Worked salary-history examples:
, 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.

Combining clauses

Clauses can be combined with AND and OR, and negated with NOT. The keywords match in any case, so and works as well as AND. NOT binds tightest, then AND, then OR, so:
parses as first_name = "John" OR (first_name = "Jane" AND salary_amount > 50000, today). Parentheses override precedence:
NOT goes in front of a clause or a parenthesised group. It is the way to say “does not match” for ~=, which has no negative form of its own:
NOT asks whether no entry of the field matches. On a field with a history that is not the same as !=, which asks whether some entry differs: NOT salary_amount = 1, * fails if the salary was ever 1, while salary_amount != 1, * passes if it was ever anything else. An employee who has no value for the field at all satisfies every NOT.

Filtering by integration mapping

A clause can test an employee’s mapping to one system integration instead of a property. Name the integration by its id, the one in the address of its page in Twine:
Two fields are available: Mapping clauses combine with property clauses like any other clause:
An employee with no mapping to the integration matches only = blank. So mapping(<id>).status != active leaves unmapped employees out, and NOT mapping(<id>).status = active includes them. A misspelled field or status, or an id that is not an integration id, fails the parse instead of matching nothing. A mapping has no history, so a mapping clause takes no valid_from suffix.

Worked examples

Employees whose current first name is John.
Employees matching one of the given employee numbers.
Employees who are currently employed. The value is the boolean false, not the string "false".
Employees with no employee number set.
Employees who do have an employee number.
Employees whose salary history contains any value above 50,000.
Employees whose salary effective on 2024-01-01 was above 50,000.
Employees whose first name matches the regular expression: four letters starting with “Jo”. John, Joel and joak match (the match ignores case), Jonathan does not.
Employees named John or Jane whose current salary is above 50,000.
Employees who are still employed but whose mapping to that integration is inactive.

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.
  • The Backoffice system-data employee browser.
  • The system-integration sync-preview filter.
  • The organizational unit list filter.
  • The query argument on the MCP list_employees tool.
  • The “Load employees by query” flow step.
  • The employee report filter.

Limitations and gotchas

  • NOT is not !=. See Combining clauses: NOT negates the clause over the whole field, != compares each entry.
  • An unquoted value ends at a comma, at AND or OR, or at a ) that closes a group. Quote a value that contains a standalone and or or. Parentheses inside a value are kept when they are balanced, so department = Sales (EU) works unquoted.
  • 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.
  • A clause without a date suffix ignores dated entries. Use , today for the current value; see Filtering by when a value was valid.
  • Bare negative integers are text. age > -5 compares as text; write -5.0.
  • 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) but the text query syntax always considers every id for a given key.