Formulas
Write a formula over a row and over the rows its relations point at. The full function list by category, the operators, the error codes, the caps, and why today() refreshes daily and now() does not exist.
5 min read
On this page
A formula field calculates its value from the row it is on, and from the rows that row's relations point at. Formula cells are read-only: the value is derived, and the result is stored on the row, so you can sort by it, filter on it, chart and group it on a dashboard, and read it over the API exactly like a column you typed.
Nothing you write runs as JavaScript, and no part of it reaches the database as SQL.
The short version#
- Use a normal field reference such as
{Amount}to read this row. - Use a relation such as
{Transactions}to reach a list of related rows. - Use
map,filter,some,every, orfindItemwithcurrentto inspect each related row. - Reduce that list with functions such as
sum,average,count,min, ormax. - Check the three-row preview before saving. A blank, an error, and a real zero are shown differently.
For common “show, count, or summarise related rows” questions, start with a lookup, rollup, or count. Choose Open as formula only when you need logic beyond the guided controls.
Writing one#
Add a field, choose Formula, and the editor opens with a searchable palette on two tabs: Fields — this table's fields, inserted as a reference by clicking one — and Functions, grouped by category, each showing its arguments and a one-line description.
The editor checks as you type — syntax, types, unknown references, cycles, depth and complexity — and evaluates your draft against three real rows so you can see actual values, and tell a blank apart from an error, before you save.
{Amount} * (1 + {Tax rate})if({Status} = "Paid", 0, dateBetween({Due}, today(), "days"))concat({First name}, " ", {Last name})A field is referenced by identity, not by its name. Renaming Amount to Gross updates the label the editor shows you and changes nothing about the formula.
Reading related rows#
A relation token evaluates to the list of rows it links to, and current refers to one of those rows inside map, filter, some, every and findItem.
sum(map({Invoices}, current.{Amount}))count(filter({Tasks}, not current.{Done}))join(map({Vendors}, primary(current)), ", ")That is the same machinery lookups, rollups and counts are built on — they are formulas the configurator writes for you.
Operators#
| Operators | Meaning |
|---|---|
+ - * / % ^ | Arithmetic; ^ is a power and binds tightest |
= != < <= > >= | Comparison |
and or not | Logic |
? : | Conditional, as an alternative to if |
-x | Negation |
Functions#
Control#
if, ifs, switch, empty, coalesce, isError, ifError
Text#
concat, toText, lower, upper, trim, length, contains, find, left, right, substring, replace, replaceAll, repeat, split, regexMatch, regexExtract, regexReplace, format
Number#
toNumber, abs, round, roundUp, roundDown, floor, ceil, mod, pow, sqrt, exp, ln, log10, sign
Date#
today, dateAdd, dateSubtract, dateBetween, formatDate, parseDate, startOf, endOf
dateAdd, dateSubtract, dateBetween, startOf and endOf take a unit: milliseconds, seconds, minutes, hours, days, weeks, months, quarters, years or business_days. Omit it and you get days.
List#
Shape: map, filter, some, every, findItem, first, last, at, slice, reverse, sort, unique, compact, flat, includes, join
Counts: count, countValues, countUnique, countEmpty, percentEmpty, percentNotEmpty
Numbers: sum, average, median, mode, min, max, range, stddev, stddevPop, variance, variancePop
Dates: earliest, latest, dateRange
Checkboxes: all, any, countChecked, countUnchecked, percentChecked, percentUnchecked
Options: countPerOption, percentPerOption
Related rows#
primary (the name of a related row), rowId (its identifier)
Empty lists answer rather than error: the numeric reductions are blank, the count family is 0, all is true and any is false.
Dates: today() but not now()#
today() is the only function whose answer changes on its own. It returns today's date in the workspace timezone, and rows using it are recalculated once a day at midnight. So dateBetween({Due}, today(), "days") is right every morning without anyone touching the table.
There is no now(). A to-the-second clock would make every row containing it permanently out of date, which would mean either recalculating your whole table continuously or showing you a number that is quietly wrong. Neither is worth it, so the function does not exist rather than existing and misleading you.
Errors#
An error is printed in the cell, using spreadsheet conventions, and is reported as a stable code over the API.
| Cell | Meaning |
|---|---|
#DIV/0! | Division by zero. |
#VALUE! | The value could not be used — a negative square root, an unparseable date. |
#REF! | A field or table the formula reads has been deleted or trashed. |
#TYPE! | A value was the wrong type for what the formula asked of it. |
#LIMIT! | The formula is more complex than the engine will evaluate. |
#DEPTH! | The formula reaches further than 5 hops from its own row. |
#LINKS! | A relation cell it traverses holds more links than the engine will follow. |
#N/A | The value is not available. |
Use isError and ifError to handle one deliberately.
#REF! is recoverable: restore the trashed field or table and the value comes back. Deleting a field that a formula reads is refused until you have seen how many computed fields read it and confirmed.
While it recalculates#
A cell that owes a recalculation reads Recalculating. It never renders as a blank, because a blank is a real answer that some formulas legitimately give, and you would have no way to tell the two apart.
Editing a formula, or changing a field it depends on, marks the affected rows and works through them. Small tables finish before the save returns; large ones fill in as the work drains.
Caps#
Every one is a refusal with a reason, never a partial answer.
| Limit | Value |
|---|---|
| Formula length | 4,000 characters |
| Distinct fields one formula may reference | 100 |
| Expression size | 500 nodes |
| Expression nesting | 32 levels |
| Hops across tables | 5 |
| Links followed per relation cell | 100 |
| Regular-expression pattern length | 256 characters |
Regular expressions run on a restricted syntax with a step budget: no backreferences, no lookaround, and a pattern that would take too long to match is refused rather than allowed to run.
Cycles — two fields that would each need the other's answer — are rejected when you save, not discovered later.
Where to go next#
- Lookups, rollups and counts — the same engine, configured rather than typed.
- Field types in a table — what each kind gives a formula to work with.
Use this page with your AI assistant
Every BankSync doc is available as plain Markdown for agents and LLMs.