Formula functions

Formula functions

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.

Family

Functions

Logic

10

Lists

6

Count maps

3

Document state

4

Math

11

Text

15

Today & now

3

Date parts

6

Date arithmetic

4

Date difference

2

Working calendars

3

People directory

2

Lookups

4

Cross-table aggregates

8

Prices, currency & tax

3

Ledger balances

1

Units

1

Scheduling

2

Total

88

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.

· · · · .