Who can do this: Owner on the space or Manage on the table, to write a formula in a Formula column (saving a table's columns needs the owner right on that table). Owner for formulas in automations (the Automations menu). See the .
What it's for
A function is a ready-made calculation that you call by name and give values in brackets, for example LOOKUP({Product}, "Price"). This page lists every function the formula editor offers: its name, how to write it, and what it does. How formulas are built (column references, operators, quoting, worked recipes) is on ; this page is the dictionary to go with it.
The signatures and descriptions below are the text of the in-app function reference, so what you read here is what the editor shows. Some descriptions mention technical names; they are given as the product states them.
Using a function
1.Open any box that takes a formula, for example the settings of a Formula column.
2.Click Functions above the box (its tooltip is Function reference), or press Shift+F1. The panel lists every function by family. Search functions… looks through names, signatures and descriptions, in your interface language.
3.Click Insert next to a function. NAME( is placed at the cursor in the formula; type the arguments after it.
4.Or type the first letters of a name and press ⌘/Ctrl/Alt+Space. A list of matching names opens: move with the arrow keys, insert with Enter or Tab, close with Esc. Names that start with your letters come first, then names that contain them.
•Names are not case-sensitive. if, If and IF are the same function.
•Reading a signature. Arguments go in the order shown; they are passed by position. In [square brackets] they are optional (the panel marks them optional). … means the last argument can be repeated (the panel marks it repeats).
•Names and signatures are never translated. Only the descriptions are, in every interface language.
•The panel knows where you are. For the place you are editing, it marks a function Conditions apply or Not available here and says why. Old names show as Also accepted: …. It remembers in your browser whether it was open.
Functions at a glance
The catalog holds 88 functions in 18 families. Click a family to jump to it.
All functions by family
Each description is quoted as the product states it, including the examples it contains. Text in {braces} is a column of the current row; @name refers to a column of the other table in the cross-table and lookup functions. Both are explained in .
Logic
Pick between values, combine conditions, name intermediate results so a long formula stays readable, and cope with blanks and errors.
Function | Signature | What it does |
|---|
IF
| IF(condition, then, [else])
| Returns one value if condition is true, another if false. |
IFS
| IFS(condition1, value1, condition2, value2, …)
| Returns the value of the FIRST condition that is true — the readable alternative to nested IFs. Finish with TRUE, <default> to catch everything else; with no match the result is blank. |
AND
| AND(a, b, …)
| Returns true only if ALL arguments are true. |
OR
| OR(a, b, …)
| Returns true if ANY argument is true. |
NOT
| NOT(value)
| Reverses a boolean value. |
LET
| LET(name, value, [name, value…], result)
| Names intermediate results so a long formula stays readable and each value is computed once. E.g. LET(net, {Qty}*{Price}, tax, net*0.2, net+tax). |
ISBLANK
| ISBLANK(value)
| Returns true if value is empty or null. |
ISERROR
| ISERROR(value)
| Returns true if evaluating value fails (e.g. it reads a column whose own formula errored). |
IFERROR
| IFERROR(value, fallback)
| Returns value, or fallback if evaluating value fails. |
COALESCE
| COALESCE(a, b, …)
| Returns the first non-empty value in the list. |
Lists
For cells that hold several items (a multi-select, a list of linked rows, people). They match whole items, whereas CONTAINS looks for text inside the joined string. ARRAY_UNION and ARRAY_EXCEPT return a changed list; the other four only read a list.
Function | Signature | What it does |
|---|
ARRAY_INCLUDES
| ARRAY_INCLUDES(list, value)
| TRUE when the list cell contains exactly this value. Unlike CONTAINS it matches whole items, so ARRAY_INCLUDES({Types}, "Трек") is FALSE for ["Трековая гонка"]. Case-insensitive. On a reference list it matches by row identity: ARRAY_INCLUDES({Coaches}, {Coach}) is true when that exact row is linked, however it is labelled now. |
ARRAY_INCLUDES_ANY
| ARRAY_INCLUDES_ANY(list, values)
| TRUE when the two lists share at least one item — e.g. ARRAY_INCLUDES_ANY(LOOKUP({Package}, "Types"), {Session types}). A single value is treated as a one-item list. |
ARRAY_LENGTH
| ARRAY_LENGTH(list)
| How many items the list cell holds (0 when empty). |
ARRAY_JOIN
| ARRAY_JOIN(list, [separator])
| Joins the list into text, using ", " unless another separator is given. |
ARRAY_UNION
| ARRAY_UNION(list, value, …)
| Adds items to a list without duplicating what is already there, and RETURNS the new list — the others above only read a list. Reference and person items match by identity, everything else by label (case-insensitive). Mainly for the update_related_rows automation action, e.g. ARRAY_UNION(@{Favourite coaches}, {Coach}) adds this booking's coach to the client's list, leaving the coaches already there untouched. |
ARRAY_EXCEPT
| ARRAY_EXCEPT(list, value, …)
| Removes items from a list and RETURNS the new list — the mirror of ARRAY_UNION, matched the same way. E.g. ARRAY_EXCEPT(@{Favourite coaches}, {Coach}) drops this booking's coach from the client's list, leaving the rest. |
Count maps
A count map is a set of values with a count for each, for example {"Vacation": 9}. Build one with COUNTMAP, merge count maps with +, take one from another with −, and read one back with the other two functions.
Function | Signature | What it does |
|---|
COUNTMAP
| COUNTMAP(value, [weight])
| Turns a value into a count map { value: 1 }, or { value: weight } when a weight is given — e.g. COUNTMAP({Type}, WORKDAYS_BETWEEN({From}, {To})) yields {"Vacation": 9}. Add count maps together with + (merge) or − (difference) to build a breakdown. |
COUNTMAP_GET
| COUNTMAP_GET(countMap, 'value')
| Count recorded for one value in a count map (0 if absent). E.g. COUNTMAP_GET({Status total}, "Done"). |
COUNTMAP_TOTAL
| COUNTMAP_TOTAL(countMap)
| Sum of all counts in a count map (the grand total). |
Document state
For a cell that holds a list of documents with their states: list the states, count the documents, or test whether any or all of them have a given state.
Function | Signature | What it does |
|---|
DOC_STATES
| DOC_STATES(docStateCell)
| Comma-joined list of the document states in a doc-state array cell. |
DOC_COUNT
| DOC_COUNT(docStateCell, [state])
| Number of documents in a doc-state array; with state, only those matching it. |
DOC_ANY
| DOC_ANY(docStateCell, state)
| True if ANY document in the cell has the given state. |
DOC_ALL
| DOC_ALL(docStateCell, state)
| True if there is at least one document and ALL have the given state. |
Math
Rounding, absolute value, square root, remainder, and totals over several values. SUM, AVG and COUNT skip blanks.
Function | Signature | What it does |
|---|
ROUND
| ROUND(number, [decimals])
| Rounds a number to the specified decimal places (default 0). |
FLOOR
| FLOOR(number)
| Rounds down to the nearest integer. |
CEIL
| CEIL(number)
| Rounds up to the nearest integer. |
ABS
| ABS(number)
| Returns the absolute (non-negative) value. |
SQRT
| SQRT(number)
| Returns the square root. |
MOD
| MOD(number, divisor)
| Returns the remainder after dividing number by divisor. |
SUM
| SUM(a, b, …)
| Sums all numeric arguments. Blanks are skipped. In doc-editor inline tables, ranges (e.g. A1:A5) expand to a list of cell values. |
AVG
| AVG(a, b, …)
| Arithmetic mean of all numeric arguments. Blanks are skipped. |
COUNT
| COUNT(a, b, …)
| Counts how many arguments evaluate to numbers. Blanks and non-numeric values are skipped. |
MIN
| MIN(a, b, …)
| Smallest numeric argument. |
MAX
| MAX(a, b, …)
| Largest numeric argument. |
Text
Join, cut, search, replace and convert text. REGEX_EXTRACT and REGEX_REPLACE have size limits, given in their descriptions.
Function | Signature | What it does |
|---|
CONCAT
| CONCAT(a, b, …)
| Joins any number of text values into one string. |
LEN
| LEN(text)
| Returns the character count of a text string. |
UPPER
| UPPER(text)
| Converts all characters to uppercase. |
LOWER
| LOWER(text)
| Converts all characters to lowercase. |
TRIM
| TRIM(text)
| Removes leading and trailing whitespace. |
CONTAINS
| CONTAINS(text, search)
| TRUE when search appears anywhere in text. Case-insensitive. An empty search is never found, so a blank cell can't match everything. |
SEARCH
| SEARCH(search, text, [start])
| Position of search inside text, counting from 1. Case-insensitive. Returns 0 when not found — test with SEARCH(…) > 0. |
LEFT
| LEFT(text, count)
| The first count characters of text. |
RIGHT
| RIGHT(text, count)
| The last count characters of text. |
MID
| MID(text, start, [count])
| Characters from text starting at position start (counting from 1). Without count, everything to the end. |
SUBSTITUTE
| SUBSTITUTE(text, old, [new])
| Replaces every occurrence of old in text with new (default: removes it). Case-sensitive. |
REGEX_EXTRACT
| REGEX_EXTRACT(text, pattern, [group])
| The part of text captured by pattern, or empty when it doesn't match. group picks a capture group (default 1). Limits: pattern ≤ 200 characters, only the first 10 000 characters of text are searched, and nested quantifiers like (a+)+ are rejected — they can hang a large import. |
REGEX_REPLACE
| REGEX_REPLACE(text, pattern, [replacement])
| Replaces EVERY match of pattern in text with replacement (default: removes it). Use $1, $2 in the replacement to insert capture groups. Limits: pattern ≤ 200 characters, text ≤ 10 000 characters (longer input is refused, not truncated), and nested quantifiers like (a+)+ are rejected — they can hang a large import. |
TEXT
| TEXT(value)
| Converts any value to a text string. A TRANSLATABLE column reads as one language — the document's default locale, then any locale that has a value — because a formula result is stored once and read by everybody. |
VALUE
| VALUE(text)
| Parses text as a number. Recognises plain numbers, decimals, and percentages — VALUE("15%") = 0.15. Returns #VALUE if the text isn't a recognised number form. Cells holding "15%" are already auto-coerced when referenced directly (e.g. =A1*200 works); VALUE is for explicit parsing of constructed strings. |
Today & now
Today's date and the current moment. This family also holds THIS_ROW, which is not a date: it returns the id of the current row.
Function | Signature | What it does |
|---|
TODAY
| TODAY()
| Returns today's date as YYYY-MM-DD. |
NOW
| NOW()
| Returns the current date and time as an ISO string. |
THIS_ROW
| THIS_ROW()
| Returns the current row's own id — the uuid the database stores against this row. Zero-arity, no arguments. Meant for cross-table filters that need to match the current row itself. Before reaching for it, note that @order == THIS_ROW() against a reference back at this table is a backlink: a rollup column (to aggregate the children) or a lookup column (to read one) does that through the reference index, refreshing in the same write — prefer those. THIS_ROW() is for when the result has to be a formula — an expression aggregate, an ORDER BY pick, arithmetic around the call: SUM_TABLE("Payments", @amount * @fx, @invoice == THIS_ROW()), FIND_VALUE("Attachments", "url", @entity == THIS_ROW(), "created_at", "DESC"). Returns null on client-preview (no row yet) — comparisons short-circuit to no-match, no throw. |
Date parts
Pick one part out of a date. HOUR and MINUTE are in UTC.
Function | Signature | What it does |
|---|
YEAR
| YEAR(date)
| Returns the 4-digit year. |
MONTH
| MONTH(date)
| Returns the month as a number (1 = January, 12 = December). |
DAY
| DAY(date)
| Returns the day of the month (1–31). |
HOUR
| HOUR(datetime)
| Returns the hour component (0–23, UTC). |
MINUTE
| MINUTE(datetime)
| Returns the minute component (0–59, UTC). |
WEEKDAY
| WEEKDAY(date)
| Returns the ISO weekday number (1 = Monday, 7 = Sunday). |
Date arithmetic
Build a date, or move one by a number of days, months, years, hours or minutes.
Function | Signature | What it does |
|---|
DATE
| DATE(year, month, day)
| Constructs a date string from year, month, and day components. |
DATEADD
| DATEADD(date, n, unit)
| Adds n units to a date. Use a negative n to subtract. |
EDATE
| EDATE(date, months)
| Returns the same day of the month, offset by a number of months. |
EOMONTH
| EOMONTH(date, months)
| Returns the last day of the month, optionally offset by months. |
Date difference
How far apart two dates are, in days or in another unit.
Function | Signature | What it does |
|---|
DAYS
| DAYS(date1, date2)
| Returns whole days from date1 to date2 (positive when date2 is later). |
DATEDIF
| DATEDIF(start, end, unit)
| Returns the difference between two dates in the given unit. |
Working calendars
Count working days and test whether a date is a working day, using the space's working calendar (weekends and holidays). Each takes an optional calendar argument. The calendars are set up under Working Calendars; see .
Function | Signature | What it does |
|---|
WORKDAYS_BETWEEN
| WORKDAYS_BETWEEN(start, end, [calendar])
| Counts working days in the inclusive [start, end] range using the project working calendar (weekends + holidays). Returns 0 when start > end. |
WORKDAYS_IN
| WORKDAYS_IN(period, [calendar])
| Counts working days in a whole period using the project working calendar (weekends + holidays). period is "YYYY-MM" for a month, "YYYY" for a year, or any date — in which case the month containing it is used. Use it to derive a monthly working-days norm from the calendar, or to check a hand-entered norm against it. |
IS_WORKING_DAY
| IS_WORKING_DAY(date, [calendar])
| Returns true when the date is a working day (not a weekend or holiday) in the resolved working calendar. |
People directory
Read from the space's Person Directory: a person's working calendar and their line manager. They need a Person Directory; see .
Function | Signature | What it does |
|---|
CALENDAR_OF
| CALENDAR_OF(person, [format])
| Returns the working calendar configured for person in the project's default Person Directory. Default returns the calendar's numeric id (composes with WORKDAYS_BETWEEN); pass "name" to get the human-readable display name instead. Returns null when the person isn't in the directory or has no calendar set. |
MANAGER_OF
| MANAGER_OF(person, [directory])
| Returns the line manager for person via the hierarchy of the named Person Directory (or the project default when [directory] is omitted). Chain it — MANAGER_OF(MANAGER_OF({p})) — for skip-level. Pass [directory] to read a matrix-org / service-desk hierarchy. |
Lookups
Read values from another table: follow a reference on this row (LOOKUP), or find a row by matching a value. In the functions that name a table, the table is written in quotes (or as its numeric id) and must be typed out, not taken from a column.
Function | Signature | What it does |
|---|
LOOKUP
| LOOKUP(referenceColumn, resultColumn)
| Follows a reference field on THIS row and returns one column from the row it points at. Unlike LOOKUP_TABLE, it resolves BY the referenced row's id (not by a matched value), so there is no ambiguity and no need to name the other table — the target table comes from the reference column itself. referenceColumn must be a column of the current table that holds a reference with a target table — a reference column, a lookup that copies one, or a formula column declared formulaResultType: "reference" with referenceDocumentId — written as a {column} reference. An inline expression is refused at save: it has no column config naming the table, so it could never resolve. To look up through a computed reference, give it its own reference-declared formula column and pass that. A multi-value reference uses its first link. resultColumn is a quoted display name (case-insensitive) or column id on the referenced table. Returns null when the reference is empty or the column is unknown. Example: LOOKUP({Product}, "Price"). |
LOOKUP_TABLE
| LOOKUP_TABLE(table, matchColumn, matchValue, resultColumn)
| Generic VLOOKUP: finds the FIRST row in another data table whose matchColumn equals matchValue, and returns its resultColumn. For matching on a VALUE (a SKU, a code). When this row already has a reference into that table, use LOOKUP({ref}, "col") or a lookup column instead — they follow the link by row id, with no value matching, and refresh only when the column they read changes. The table is named by a quoted table name (case-insensitive) or numeric document id — it MUST be a literal so the value can be loaded. Column arguments are quoted display names (case-insensitive) or column ids. Returns null when the table/column is unknown or no row matches. Example: LOOKUP_TABLE("Products", "SKU", {sku}, "Price"). |
LOOKUP_ROW
| LOOKUP_ROW(table, matchColumn, matchValue, [labelColumn])
| Like LOOKUP_TABLE, but returns a REFERENCE to the first matching row (not one of its columns). Finds the FIRST row in another data table whose matchColumn equals matchValue, and returns a reference cell pointing at that row. By default the reference is labelled by the row's title column; pass an optional labelColumn to label it by a specific column instead (like a reference column's display column). The table is named by a quoted table name (case-insensitive) or numeric document id — it MUST be a literal so the value can be loaded. Column arguments are quoted display names (case-insensitive) or column ids. Set the column's result type to Reference to render it as a clickable chip; the result is id-matchable by a downstream LOOKUP_TABLE. Returns null when the table/column is unknown or no row matches. Example: LOOKUP_ROW("Products", "SKU", {sku}, "Наименование кратко"). |
WHATIF
| WHATIF(table, matchColumn, matchValue, overrideColumn, overrideValue, resultColumn)
| Scenario VLOOKUP: finds the FIRST row in another data table whose matchColumn equals matchValue, RECOMPUTES that row with overrideColumn set to overrideValue, and returns the recomputed resultColumn. Answers "if input X were Y on the matched row, what would output Z be?" — e.g. the profit on a price-list model if the sale price were a hypothetical value, holding the cost side constant. The overridden column is PINNED to your value (it may be an input OR a derived/formula column); every other column keeps the matched row's value and the override's dependent formulas recompute. The table is named by a quoted name (case-insensitive) or numeric document id and MUST be a literal. Column arguments are quoted display names (case-insensitive) or column ids. Returns null when the table/column is unknown or no row matches. Example: WHATIF("Расчёт прайс", "Model No.", {model}, "Продажа МСГ, INR", {my_price}, "% финрез"). |
Cross-table aggregates
Total, count, average, minimum and maximum over the rows of another table that match a condition, and fetch one row by condition. When a reference already links the rows, several descriptions point you to a rollup or lookup column instead. JOIN_SUM is refreshed only on request: see Where each function works.
Function | Signature | What it does |
|---|
SUM_TABLE
| SUM_TABLE(table, aggregateExpression, predicate)
| Sums aggregateExpression across rows of another data table that match predicate. A reference already linking the rows is the better route: to total the rows that point AT this row, use a backlink rollup column (fn sum, conditions in its filter) — it reads them by id instead of scanning this table per row, and refreshes in the same write. SUM_TABLE is for matching on a VALUE (a SKU, a date window, a status across the whole table). The predicate is a WHERE-style expression: target-table columns use @col / @{Column Name} refs; the current row's columns still use {Column Name}. Supports ==, !=, >, >=, <, <=, AND / OR / NOT, and the sugar IN(@col, "a", "b", …), NOT_IN(@col, …), BETWEEN(@col, lo, hi), IS_NULL(@col), IS_NOT_NULL(@col). A comparison that reads ONLY the current row ({all_time} == true, {n} > 0) is allowed and is evaluated once per row as a constant — useful to gate a branch: (@date >= {from} AND @date < {to}) OR ({all_time} == true AND IS_NULL(@date)). The sugar calls, though, still need an @col first argument. A reference target column matches by the referenced ROW ID, so @client == "Acme" finds nothing — compare against a reference cell (@client == {client}) or a REF("Acme") literal, which also matches on label. The table is named by a quoted table name (case-insensitive) or numeric document id — it MUST be a literal so the target can be pre-loaded. aggregateExpression is either a bare numeric column (quoted name or column id) or an arithmetic expression over @col refs on the target and scalars from the current row — e.g. @remaining_qty * @price, @qty * (1 - @discount_pct), @amount * FX_RATE(@ccy, "USD"). Operators: + - * / and unary -; division-by-zero yields NULL (row drops out of the sum). All @col refs used in the expression must be numeric. Returns 0 when nothing matches, null when the table / expression cannot be resolved. Examples: SUM_TABLE("Sales", "qty", @sku == {sku} AND @date >= {period_start}) (single column) — SUM_TABLE("Stock", @remaining_qty * @price, @sku == {sku}) (inventory value: SUMPRODUCT of qty and price). |
JOIN_SUM
| JOIN_SUM(leftTable, rightTable, joinPredicate, expression, [requireMatch])
| Correlated inner-join aggregate: joins two data tables on joinPredicate, evaluates expression over both sides, and sums it. Three column scopes — {col} = the current row, @col = the LEFT table, #col = the RIGHT table. Both tables are named by a quoted name (case-insensitive) or numeric document id — each MUST be a literal so the tables can be pre-loaded. joinPredicate is a WHERE-style expression mixing all three scopes (correlate the left to the current row and equi-join left↔right), e.g. @bom == {bom} && #material == @component && #month == {month}. Combine terms with && / || / ! (or the AND(…) / OR(…) / NOT(…) function forms); comparisons == != > >= < <=; sugar IN/NOT_IN/BETWEEN/IS_NULL/IS_NOT_NULL. expression is arithmetic over @col / #col numeric columns (and current-row scalars): + - * /, unary -; division-by-zero drops the row. INNER JOIN semantics: a left row matching more than one right row raises #E_NOTUNIQUE; a left row matching zero right rows contributes 0, unless the optional 5th arg requireMatch is TRUE, which raises #E_NOMATCH. REFRESH-ONLY: never recomputes reactively — only on an explicit row Refresh (or on create). The value is a snapshot. Example (BOM standard cost): JOIN_SUM("BOM lines", "Material prices", @bom == {bom} && #material == @component && #month == {month}, @qty_per * #price). |
COUNT_TABLE
| COUNT_TABLE(table, predicate)
| Counts rows of another data table that match predicate. Same predicate syntax as SUM_TABLE — target-side columns use @col / @{Column}, current-row columns use {Column}. A reference already linking the rows is the better route: to count the rows that point AT this row, use a backlink rollup column (fn count, conditions in its filter) — it reads them by id instead of scanning this table per row, and refreshes in the same write. The table is named by a quoted table name (case-insensitive) or numeric document id — it MUST be a literal. Returns 0 when nothing matches, null when the table is unknown. Example: COUNT_TABLE("Bookings", @slot_date == {date} AND @status IN ("confirmed", "checked_in")). |
AVG_TABLE
| AVG_TABLE(table, aggregateExpression, predicate)
| Averages aggregateExpression across rows of another data table that match predicate. Same shape as SUM_TABLE — see it for full syntax, and for when a backlink rollup (fn avg) is the better choice. Accepts a bare numeric column OR an arithmetic expression over @col refs (e.g. @qty * @price for a mean line-value). Returns null when no rows match or the target is unknown. |
MIN_TABLE
| MIN_TABLE(table, aggregateExpression, predicate)
| Returns the minimum aggregateExpression value across rows of another data table that match predicate. Same shape as SUM_TABLE — see it for when a backlink rollup (fn min) is the better choice. The aggregate must be NUMERIC. A date column is accepted by the parser but always yields a blank cell — the aggregate is read back as a number and a date is not one, so use FIND_VALUE ordered by the date column to get the earliest date instead. An expression arg (e.g. @qty * @price) is numeric only. Returns null when no rows match. |
MAX_TABLE
| MAX_TABLE(table, aggregateExpression, predicate)
| Returns the maximum aggregateExpression value across rows of another data table that match predicate. Same shape as SUM_TABLE — see it for when a backlink rollup (fn max) is the better choice. The aggregate must be NUMERIC. A date column is accepted by the parser but always yields a blank cell — the aggregate is read back as a number and a date is not one, so use FIND_VALUE ordered by the date column to get the latest date instead. An expression arg (e.g. @qty * @price) is numeric only. Returns null when no rows match. |
FIND_VALUE
| FIND_VALUE(table, resultColumn, predicate, [orderByColumn], [orderDir="ASC"])
| Finds ONE row in another data table matching predicate and returns its resultColumn value. To read the first row that REFERENCES this row (no ORDER BY), a backlink lookup column does it through the reference index — prefer that; FIND_VALUE is for matching on values or picking by an order. The predicate is a WHERE-style expression: target-table columns use @col / @{Column Name} refs; the current row's columns still use {Column Name}. Supports ==, !=, >, >=, <, <=, AND / OR / NOT, plus IN, NOT_IN, BETWEEN, IS_NULL, IS_NOT_NULL — same syntax as SUM_TABLE. Pass orderByColumn (a quoted column name / id) to pick the row with the largest / smallest value on that column — otherwise the first row by rowOrder wins. orderDir is "ASC" (default) or "DESC". Row-order ASC breaks ties deterministically. The table is named by a quoted table name (case-insensitive) or numeric document id — it MUST be a literal so the target can be pre-loaded. Returns null when the table / result column is unknown or no row matches. Example — previous stocktaking qty for the same material: FIND_VALUE("Stock", "qty", @material == {material} AND @date < {date}, "date", "DESC"). |
FIND_ROW
| FIND_ROW(table, predicate, [orderByColumn], [orderDir="ASC"], [labelColumn])
| Finds ONE row in another data table matching predicate and returns a REFERENCE to it — same predicate + ordering as FIND_VALUE, but the whole row rather than a single column. Set the column's result type to Reference to render the result as a clickable chip; the result is id-matchable by a downstream LOOKUP_TABLE / FIND_VALUE. By default the reference is labelled by the row's title column; pass an optional labelColumn (5th arg) to label it by a specific column. Returns null when the table is unknown or no row matches. Example — reference to the previous stocktaking row for the same material: FIND_ROW("Stock", @material == {material} AND @date < {date}, "date", "DESC"). |
Prices, currency & tax
Price from a price list, exchange rate and tax rate, each read from the space's own table for that purpose. They return blank when nothing applies.
Function | Signature | What it does |
|---|
PRICE_FOR
| PRICE_FOR(priceList, product, [date], [region], [customerGroup], [qty], [currency])
| Looks up the in-force price in a pim_price_list table for the given product and dimensions. date defaults to today; qty defaults to 1; region / customerGroup / currency are optional filters. Picks the entry with the NARROWEST validity window covering date — short campaigns override standing tiers. Returns null when no row matches. |
FX_RATE
| FX_RATE(from, to, [date])
| Looks up the in-force exchange rate (units of to per 1 from) in the project's pim_fx_rates table. Resolution order: identity (from===to → 1) → direct pair → inverse pair (1/rate) → cross-via-base (one hop through the project base currency) → null. Currency codes are case-insensitive (ISO 4217). date defaults to today and picks the most-recent rate with valid_from ≤ date (FX is monotonic — newest applicable wins). Returns null when no rate resolves. Composes with PRICE_FOR — FX_RATE("EUR","USD") * PRICE_FOR(101, {product}) converts a EUR price list to USD. |
TAX_RATE
| TAX_RATE(jurisdiction, [category], [date])
| Looks up the in-force tax rate (a fraction, e.g. 0.2 ⇒ 20%) in the project's core_tax_rate table. Resolution order: exact (jurisdiction, category) → default-category fallback (a row whose category is blank is the jurisdiction-wide default) → null. category is optional — omitting it (or passing one with no matching row) resolves the jurisdiction default. Keys are case-insensitive. date defaults to today and picks the most-recent rate with valid_from ≤ date (rates are monotonic — newest applicable wins). Returns null when no rate resolves. Composes with arithmetic — {base} * TAX_RATE("DE", "standard") adds tax to a base amount. |
Ledger balances
Reads a balance column: what is on hand, what is reserved, and what is available.
Function | Signature | What it does |
|---|
BALANCE
| BALANCE(balanceColumn, [bucket], [partition])
| Reads a balance column: "posted" (on hand, the default and what the grid shows), "reserved" (soft holds) or "available" (posted − reserved). A third argument reads ONE partition instead of the total, named by its STORED value: for a partition by a reference (a warehouse) that is the linked ROW ID — a reference column such as {Warehouse}, or REF("<row id>", "Main"), never the name alone; for a select, the option value. A value matching no partition reads 0, exactly like a partition with no movement (a name saved as the argument is refused with the id to use). E.g. BALANCE({Stock}, "available", {Warehouse}). Works on another table too: BALANCE(LOOKUP({Package}, "Classes left"), "available"). To show one partition, or the sum of several, as its own column that sorts and filters, add a Balance lookup column reading "This row" (parentSource:'self') instead. |
Units
Convert a number between units of mass, length, area, volume, time and pieces.
Function | Signature | What it does |
|---|
UOM_CONVERT
| UOM_CONVERT(value, from, to)
| Converts a numeric value between linear units (mass / length / area / volume / time / pieces). Unit codes are case-insensitive. Returns null for non-numeric values, unknown codes, or incompatible dimensions (e.g. kg → m). Supported codes:• mass — kg, g, mg, t, lb, oz• length — m, mm, cm, km, in, ft, yd, mi• area — m2, cm2, km2, ha, ft2, acre• volume — m3, ml, l, cm3, gal_us, gal_uk, fl_oz_us• time — s, ms, min, h, d, wk• pieces — pc, pair, dozen, grossAffine units like temperature are NOT supported. Compose with arithmetic — UOM_CONVERT({weight}, "kg", "lb") * 0.99. |
Scheduling
Check whether a resource is already booked in a time interval, using the space's scheduling (exclusive-booking) tables. They return blank when the space has no such table.
Function | Signature | What it does |
|---|
IS_RESOURCE_FREE
| IS_RESOURCE_FREE(resource, start, end)
| Returns true when NO non-cancelled booking overlaps the half-open interval [start, end) for resource, across the project's scheduling (exclusive-booking) tables. The mirror of the write-side double-booking guard, for reads — use it to flag clashes or show availability before a slot is saved. Touching endpoints (one booking ends exactly when the next begins) do NOT overlap. Cancelled bookings free their slot. Returns null when the project has no scheduling table, the resource is empty, or the interval is incomplete. |
BOOKING_COUNT
| BOOKING_COUNT(resource, start, end)
| Returns the number of non-cancelled bookings that overlap the half-open interval [start, end) for resource, across the project's scheduling (exclusive-booking) tables. 0 means the resource is free (compose with IS_RESOURCE_FREE, or use the count directly for utilisation). Touching endpoints do NOT overlap; cancelled bookings are excluded. Returns null when the project has no scheduling table, the resource is empty, or the interval is incomplete. |
Units in DATEADD and DATEDIF
The unit argument is text: "day", "month", "year", "hour" or "minute". It is not case-sensitive and a plural is accepted ("days"). Short forms d, m / mon, y / yr, h and min also work. An unknown unit is an error that lists the five names. In DATEADD, months and years must be whole numbers; days, hours and minutes can be fractions.
Legacy names (aliases)
Three functions were renamed. The old name is still accepted, and a formula already saved with it keeps working as written: the product does not rewrite stored formulas. The editor and the function reference offer only the new name.
Old name | Current name |
|---|
TALLY
| COUNTMAP
|
TALLYGET
| COUNTMAP_GET
|
TALLYTOTAL
| COUNTMAP_TOTAL
|
Use the current name in anything new.
Where each function works
The formula language is the same everywhere, but it runs in five places, and they do not all know how to fetch data from outside the current row. The editor restricts only the functions below, which read from outside the row (other tables, calendars, price lists…) or need the row itself. It does not restrict any other function in any of the five places.
Place | What it is |
|---|
Formula column | A Formula column on a table. The default |
Filter | A filter expression: the Filter (optional) of a rollup or lookup column, the Filter formula of a reference column, and similar conditions |
Automation field | A formula-mode value in an automation action (such as Set fields on this row), or a {{= … }} token in an agent task |
Automation guard | The formula of a guard (Guards) that checks a row before a rule runs |
Document cell | A Cell formula in a table inside a document: A1 references (A1, B2:C5), no row and no columns |
Yes means the function works there. No means it cannot produce a value there: the result is always blank, and the editor greys the function out with the reason.
Function | Formula column | Filter | Automation field | Automation guard | Document cell |
|---|
THIS_ROW
| Yes | Yes | Yes | Yes | No |
WORKDAYS_BETWEEN
| Yes | Yes | No | No | No |
WORKDAYS_IN
| Yes | Yes | No | No | No |
IS_WORKING_DAY
| Yes | Yes | No | No | No |
CALENDAR_OF
| Yes | Yes | No | No | No |
MANAGER_OF
| Yes | Yes | No | No | No |
LOOKUP
| Yes | Yes | Yes | No | No |
LOOKUP_TABLE
| Yes | Yes | No | No | No |
LOOKUP_ROW
| Yes | Yes | No | No | No |
WHATIF
| Yes | Yes | No | No | No |
PRICE_FOR
| Yes | Yes | No | No | No |
FX_RATE
| Yes | Yes | No | No | No |
SUM_TABLE
| Yes | Yes | No | Yes | No |
JOIN_SUM
| Only with “Disable automatic recalculation” | No | No | No | No |
COUNT_TABLE
| Yes | Yes | No | Yes | No |
AVG_TABLE
| Yes | Yes | No | Yes | No |
MIN_TABLE
| Yes | Yes | No | Yes | No |
MAX_TABLE
| Yes | Yes | No | Yes | No |
FIND_VALUE
| Yes | Yes | No | Yes | No |
FIND_ROW
| Yes | Yes | No | Yes | No |
TAX_RATE
| Yes | Yes | No | No | No |
IS_RESOURCE_FREE
| Yes | Yes | No | No | No |
BOOKING_COUNT
| Yes | Yes | No | No | No |
Every function not in this table is unrestricted in all five places.
•JOIN_SUM needs a manual refresh. In a formula column it works only if you tick Disable automatic recalculation on that column. The value is then a snapshot: refresh it from the cell, with the row Refresh action, or with a Recompute row automation. Without the tick the product refuses to save the column, because an automatic pass would blank the value.
•Reference columns allow even fewer. In the Filter formula of a reference column (and of a reference input of an automation) the only function that reads from another table is LOOKUP. The save is refused if the formula uses another.
•Guards are refused at save. A guard that uses a function marked No is not accepted when you save the rule, so a rule cannot look guarded when it is not.
•Automation fields. Of the functions that read from outside the row, only LOOKUP, ROWS_JSON and DOC_TEXT work in an automation field. To use any other, compute it in a formula column and read that column in the rule.
Two functions that are not in the picker
ROWS_JSON and DOC_TEXT are understood only in automation fields, and the function reference does not list them. Both are meant for putting data into an AI agent's task text.
Function | Signature | What it does |
|---|
ROWS_JSON
| ROWS_JSON(table, predicate, [col1, col2, …], [maxChars])
| Returns the rows of another table that match the condition as a JSON text, keeping only the columns you name (all columns if none). The condition is written like the one in SUM_TABLE. The result is capped in rows and characters; a trailing number sets the character cap for this call. Blank when nothing resolves. Not recalculated automatically: read when the rule runs |
DOC_TEXT
| DOC_TEXT(document, [maxChars])
| Returns the body of a text document as Markdown, cut to a character cap (the optional second argument overrides it). document is a numeric id, a slug or an exact name. Blank on any miss. Not recalculated automatically: read when the rule runs |
In a formula column they return nothing, which is why they are kept out of the picker.
· · · · .