A Formula field computes its value from the other fields of the same row — days since a date, a price with VAT, a full name, a list of tags. The function names are Airtable's, so a formula you know from there works here.
Add field → Formula, then type the expression. Refer to a field with its name in braces: {Amount} * 1.21. Type { or the start of a name and the editor suggests fields and functions, with what each gives (number, text, date, …). Inside a function it shows its arguments, the one you are typing highlighted.
Text goes in quotes ("done"), & joins text, + - * / % calculate, = != < <= > >= compare, &&, || and ! mean AND, OR and NOT. A list of values is written [1, 2, {Price}]; multi-select, multiple-user and file fields are lists too.
A mistake is underlined where it is, with what is wrong, e.g. DATEADD expects a date as argument 1; {Intake} is text. While you type, the result shows on three rows of your list (choose which), formatted as the column will show it.
Linked rows. A Link to another list is a list of the rows it links to (each shows as its name). Inside FILTER, MAP, SORT, REDUCE, ANY and ALL over it, item.Field reads a field of the linked row: COUNTA(FILTER({Tasks}, item.Status = "Open")), SUM(MAP({Lines}, item.Qty * item.Price)), SORT({Tasks}, item.Due). The value follows when a linked row or a link changes, without editing the row.
Pasted from Excel? =SUMIF(…), =AVG(…), =IFS(…) and =CONCAT(…) work as they are (the = is dropped). Cell references like A1 or B2:B10 do not: use the field, {Amount}.
TODAY() and NOW() keep the field up to date by themselves: formulas with TODAY update daily, with NOW every 15 minutes.
The editor works out what a formula gives, and the column becomes that type, so it sorts, filters and shows like one:
| Result | What it can do | Example |
|---|---|---|
| Number | Sorts, filters and totals as a number; decimals, percentage or a currency | ROUND({Price} * 1.21, 2) |
| Text | Everything text can do | TRIM({First name} & " " & {Last name}) |
| True / false | A checkbox (or Yes / No); filters "is" / "is not" | AND({Paid}, {Amount} > 100) |
| Date, date and time | Date filters; calendar, timeline and Gantt views; your date format and timezone | DATEADD({Start}, 14, "days") |
| List of values | Chips (or joined); filters "has any / all / none of" | FILTER({Tags}, item != "internal") |
Under the preview you choose how the result is shown: decimals, percentage or currency; a date format and timezone; a checkbox or Yes / No; chips or text.
Click ✦ Describe it instead and say what you want, e.g. days in shelter: from intake to adoption date, or until today if not adopted yet. Flexlists writes the formula, checks it against your fields and shows it with an explanation and the result on three rows: Use it, Edit or Try again. A formula that does not work is never shown. Your description is saved with the field (✦ written by AI from: …), so you can change it and regenerate later, also after renaming a field.
✦ Explain tells you in plain words what any formula does. When a formula gives an error such as #VALUE!, ✦ Fix it proposes a correction and shows what changes before you apply it.
Privacy: with Send sample values on (the default) up to three rows' values go with the request, so the AI sees what your data looks like; switch it off to send only the names and types of the fields. The formula AI is included in every plan, up to 30 requests per person per hour. Dave (the assistant) and AI agents (MCP) use the same: ask "add a field that shows how many days each animal has been in the shelter".
A cell shows an error code when a row cannot be computed; it passes through everything that uses it. Catch one with IFERROR(value, fallback).
#VALUE! | A value of the wrong kind, e.g. text where a date is needed. Text is not a date: use DATETIME_PARSE({Text field}). |
#DIV/0! | Divided by zero. |
#NUM! | Not a number, e.g. SQRT(-1). |
#NAME? | An unknown function. |
#REF! | A field that does not exist (any more). |
#CYCLE! | The formula (indirectly) uses itself. |
#ERROR! | Anything else, e.g. ERROR() or a row that took too long. |
Names are case-insensitive; Excel and Baserow names work as aliases (AVG, CONCAT, …). Positions count from 1. "Gives" is the type of the result (any: it depends on the arguments).
| Function | Gives | What it does | Example |
|---|---|---|---|
ABS(value) | number | The value without its sign | ABS({Balance}) |
AVERAGE(value1, value2, ...)also AVG | number | Average of the values (blanks are skipped); also AVG | AVERAGE({Q1}, {Q2}, {Q3}) |
CEILING(value, [significance]) | number | Rounds up to the nearest multiple of significance (1 when left out) | CEILING({Hours}, 0.25) |
COUNT(value1, value2, ...) | number | How many of the values are numbers | COUNT({Q1}, {Q2}, {Q3}) |
COUNTA(value1, value2, ...) | number | How many of the values are not empty (the length of an array) | COUNTA({Tags}) |
COUNTALL(value1, value2, ...) | number | How many values there are, empty ones included | COUNTALL({Tags}) |
EVEN(value) | number | Rounds away from zero to the nearest even integer | EVEN({Guests}) |
EXP(power) | number | e raised to the power | EXP(1) |
FLOOR(value, [significance]) | number | Rounds down to the nearest multiple of significance (1 when left out) | FLOOR({Price}, 5) |
INT(value) | number | The largest integer not above the value | INT({Hours}) |
LOG(number, [base]) | number | Logarithm in a base (10 when left out) | LOG(1000) |
LN(number) | number | Natural logarithm | LN({Growth}) |
MAX(value1, value2, ...) | any | The largest value (the latest, for dates) | MAX({Score}, 0) |
MEDIAN(value1, value2, ...) | number | The middle value | MEDIAN({Q1}, {Q2}, {Q3}) |
MIN(value1, value2, ...) | any | The smallest value (the earliest, for dates) | MIN({Price}, 100) |
MOD(value, divisor) | number | The remainder after dividing (it has the sign of the divisor) | MOD({Row}, 2) |
ODD(value) | number | Rounds away from zero to the nearest odd integer | ODD({Seats}) |
POWER(base, power) | number | base raised to power | POWER({Side}, 2) |
ROUND(value, decimals) | number | Rounds to a number of decimals (0 to 10) | ROUND({Amount} * 1.21, 2) |
ROUNDDOWN(value, [decimals]) | number | Rounds toward zero (negative decimals round to tens, hundreds, …) | ROUNDDOWN({Price}, 1) |
ROUNDUP(value, [decimals]) | number | Rounds away from zero | ROUNDUP({Hours}, 0) |
SIGN(value) | number | 1 for a positive value, -1 for a negative one, 0 for zero | SIGN({Balance}) |
SQRT(value) | number | Square root | SQRT({Area}) |
SUM(value1, value2, ...) | number | Adds the values (and the values of arrays) | SUM({Q1}, {Q2}, {Q3}) |
VALUE(text) | number | Reads text as a number ("€1,250.50" → 1250.5, "50%" → 0.5) | VALUE({Amount as text}) |
SUMIF(range, criterion, [sum_range]) | number | Adds the values (of sum_range) where range meets the criterion (5, ">5", "<>x", "a*") | SUMIF({Scores}, ">50") |
COUNTIF(range, criterion) | number | How many values meet the criterion | COUNTIF({Tags}, "urgent") |
AVERAGEIF(range, criterion, [average_range]) | number | Average of the values (of average_range) where range meets the criterion | AVERAGEIF({Scores}, ">0") |
| Function | Gives | What it does | Example |
|---|---|---|---|
CONCATENATE(text1, text2, ...)also CONCAT | text | Joins texts (same as &); also CONCAT | CONCATENATE({First}, " ", {Last}) |
ENCODE_URL_COMPONENT(text) | text | Text made safe to put in a link | "https://maps.example/?q=" & ENCODE_URL_COMPONENT({Address}) |
FIND(search, text, [start]) | number | Position of search in text (1 is the first character), 0 when not there; case-sensitive | FIND("@", {Email}) |
LEFT(text, count) | text | The first count characters | LEFT({Postcode}, 4) |
LEN(text) | number | Number of characters | LEN({Name}) |
LOWER(text) | text | Text in lower case | LOWER({Email}) |
MID(text, start, count) | text | count characters from position start (1 is the first) | MID({Code}, 3, 2) |
REPLACE(text, start, count, replacement) | text | Replaces count characters from position start | REPLACE({Phone}, 1, 1, "+31 ") |
REPT(text, times) | text | The text repeated | REPT("★", {Rating}) |
RIGHT(text, count) | text | The last count characters | RIGHT({IBAN}, 4) |
SEARCH(search, text, [start]) | number | Position of search in text ignoring case; blank when not there | SEARCH("dog", {Notes}) |
SUBSTITUTE(text, old, new, [index]) | text | Replaces old by new everywhere, or only its index-th occurrence | SUBSTITUTE({Phone}, " ", "") |
T(value) | text | The value when it is text, else empty | T({Notes}) |
TRIM(text) | text | Text without spaces at the start and end | TRIM({Name}) |
UPPER(text) | text | Text in capitals | UPPER({Code}) |
PROPER(text) | text | Every word starting with a capital | PROPER({Name}) |
TEXTJOIN(separator, ignore_empty, text1, ...) | text | Joins the values (and arrays) with a separator, optionally skipping empty ones | TEXTJOIN(", ", TRUE, {City}, {Country}) |
SPLIT(text, [separator]) | array | Splits text into an array (at commas when no separator is given) | SPLIT({Keywords}, ";") |
REGEX_MATCH(text, regex) | boolean | Whether the text matches the regular expression (RE2 syntax) | REGEX_MATCH({Email}, "^[^@]+@[^@]+$") |
REGEX_EXTRACT(text, regex) | text | The first part of the text that matches; blank when nothing does | REGEX_EXTRACT({Notes}, "[0-9]{4}") |
REGEX_REPLACE(text, regex, replacement) | text | Replaces every match ($1 is the first group) | REGEX_REPLACE({Phone}, "[^0-9+]", "") |
| Function | Gives | What it does | Example |
|---|---|---|---|
AND(value1, value2, ...) | boolean | True when every value is true (also &&) | AND({Paid}, {Shipped}) |
OR(value1, value2, ...) | boolean | True when any value is true (also ||) | OR({Urgent}, {Due} < 3) |
XOR(value1, value2, ...) | boolean | True when an odd number of the values is true | XOR({A}, {B}) |
NOT(value) | boolean | The opposite (also !) | NOT({Archived}) |
IF(condition, then, [else]) | any | One value when the condition is true, another when not | IF({Stock} > 0, "In stock", "Sold out") |
IFS(condition1, value1, ...) | any | The value of the first true condition | IFS({Score} >= 90, "A", {Score} >= 75, "B", TRUE, "C") |
SWITCH(expression, pattern1, result1, ..., [default]) | any | The result of the first pattern equal to the expression, else the default | SWITCH({Status}, "Open", 1, "Done", 2, 0) |
TRUE() | boolean | True (also written TRUE) | TRUE() |
FALSE() | boolean | False (also written FALSE) | FALSE() |
BLANK() | any | An empty value | IF({Done}, BLANK(), {Due}) |
ISBLANK(value) | boolean | True when the value is empty (blank, "" or an empty array) | ISBLANK({Email}) |
ERROR([message]) | any | An #ERROR! value, with an optional message | IF({Qty} < 0, ERROR("Negative quantity"), {Qty}) |
ISERROR(value)also ISERR | boolean | True when the value is an error (#DIV/0!, #VALUE!, …) | ISERROR({Amount} / {Qty}) |
IFERROR(value, value_if_error) | any | The value, or value_if_error when it is an error | IFERROR({Amount} / {Qty}, 0) |
| Function | Gives | What it does | Example |
|---|---|---|---|
DATETIME_PARSE(text, [format], [locale]) | date | Reads text as a date and time; format uses moment tokens (DD/MM/YYYY HH:mm) | DATETIME_PARSE({Date text}, "DD-MM-YYYY") |
DATETIME_FORMAT(date, [format]) | text | A date as text; format uses moment tokens (D MMMM YYYY, LL, h:mm A, …) | DATETIME_FORMAT({Due}, "D MMM YYYY") |
DATESTR(date) | text | The date as YYYY-MM-DD | DATESTR({Due}) |
TIMESTR(date) | text | The time as HH:mm:ss | TIMESTR({Starts}) |
YEAR(date) | number | The year | YEAR({Born}) |
MONTH(date) | number | The month, 1 to 12 | MONTH({Due}) |
DAY(date) | number | The day of the month, 1 to 31 | DAY({Due}) |
HOUR(date) | number | The hour, 0 to 23 | HOUR({Starts}) |
MINUTE(date) | number | The minute, 0 to 59 | MINUTE({Starts}) |
SECOND(date) | number | The second, 0 to 59 | SECOND({Starts}) |
WEEKDAY(date, [start]) | number | Day of the week: 0 is Sunday (or Monday with start "Monday") up to 6 | WEEKDAY({Due}, "Monday") |
WEEKNUM(date, [start]) | number | Week of the year (week 1 has January 1st; weeks start on Sunday, or Monday) | WEEKNUM({Due}) |
DATEADD(date, count, unit) | date | The date plus count units (days, weeks, months, years, hours, …) | DATEADD({Intake}, 14, "days") |
DATETIME_DIFF(date1, date2, [unit]) | number | date1 minus date2 in whole units (seconds when left out) | DATETIME_DIFF(TODAY(), {Intake}, "days") |
WORKDAY(start, days, [holidays]) | date | The working day (Monday to Friday, not a holiday) days working days after start | WORKDAY({Ordered}, 5, "2025-12-25, 2025-12-26") |
WORKDAY_DIFF(start, end, [holidays])also NETWORKDAYS | number | Working days from start to end, both counted (negative when end is earlier) | WORKDAY_DIFF({Start}, {End}) |
IS_BEFORE(date1, date2) | boolean | Whether date1 is before date2 | IS_BEFORE({Due}, TODAY()) |
IS_AFTER(date1, date2) | boolean | Whether date1 is after date2 | IS_AFTER({Delivered}, {Due}) |
IS_SAME(date1, date2, [unit]) | boolean | Whether the dates are the same (the same day, month, … with a unit) | IS_SAME({Due}, TODAY(), "day") |
TODAY() | date | Today in the formula’s timezone (updated daily) | DATETIME_DIFF(TODAY(), {Intake}, "days") |
NOW() | date | The current date and time (updated every 15 minutes) | DATETIME_DIFF(NOW(), {Created}, "hours") |
TONOW(date) | number | Days from the date until now (positive for a past date) | TONOW({Last contact}) |
FROMNOW(date) | number | Days from now until the date (positive for a future date) | FROMNOW({Due}) |
SET_TIMEZONE(date, timezone) | date | The date shown in another timezone ("Europe/Amsterdam") | DATETIME_FORMAT(SET_TIMEZONE({Starts}, "America/New_York"), "LLL") |
SET_LOCALE(date, locale) | date | The date shown in another language ("nl", "de", …) by DATETIME_FORMAT | DATETIME_FORMAT(SET_LOCALE({Due}, "nl"), "D MMMM YYYY") |
| Function | Gives | What it does | Example |
|---|---|---|---|
ARRAYJOIN(array, [separator]) | text | The items joined into text (", " when no separator is given) | ARRAYJOIN({Tags}, " · ") |
ARRAYUNIQUE(array)also UNIQUE | array | The array without repeated items | ARRAYUNIQUE({Tags}) |
ARRAYCOMPACT(array) | array | The array without empty items | ARRAYCOMPACT({Values}) |
ARRAYFLATTEN(array) | array | Nested arrays turned into one flat array | ARRAYFLATTEN([{Tags}, {Labels}]) |
ARRAYSLICE(array, start, [end]) | array | Items start to end (1 is the first item, -1 the last; end included) | ARRAYSLICE({Tags}, 1, 3) |
FILTER(array, condition) | array | The items for which the condition is true; item is the element, index its position (1 is the first) | FILTER({Tags}, item != "internal") |
MAP(array, expression) | array | The expression for every item; item is the element, index its position (1 is the first) | MAP({Tags}, UPPER(item)) |
SORT(array, [key], [order]) | array | The items sorted; key is an expression of item, order "asc" (default) or "desc" | SORT({Tags}, "desc") |
REDUCE(array, initial, expression) | any | Combines the items into one value: acc starts as initial and becomes the expression for each item; item is the element, index its position (1 is the first) | REDUCE({Scores}, 0, acc + item) |
FIRST(array) | any | The first item | FIRST({Tags}) |
LAST(array) | any | The last item | LAST({Tags}) |
INDEX(array, position) | any | The item at a position (1 is the first, -1 the last); blank past the end | INDEX(SPLIT({Full name}, " "), 1) |
CONTAINS(array, value) | boolean | Whether the array has the value as an item (or the text contains it) | CONTAINS({Tags}, "urgent") |
ANY(array, [condition]) | boolean | Whether the condition is true for any item (any item is true, without a condition); item is the element, index its position (1 is the first) | ANY({Scores}, item > 90) |
ALL(array, [condition]) | boolean | Whether the condition is true for every item; item is the element, index its position (1 is the first) | ALL({Checks}, item = "OK") |
| Function | Gives | What it does | Example |
|---|---|---|---|
RECORD_ID() | text | The id of the row | "https://example.com/rows/" & RECORD_ID() |
CREATED_TIME() | date | When the row was added | DATETIME_DIFF(NOW(), CREATED_TIME(), "days") |
LAST_MODIFIED_TIME([field1, ...]) | date | When the row last changed (of the whole row; fields are not tracked one by one) | LAST_MODIFIED_TIME() |
CREATED_BY() | text | Who added the row | CREATED_BY() |
LAST_MODIFIED_BY() | text | Who last changed the row | LAST_MODIFIED_BY() |
More: Linking lists (Rollup, Count, Lookup) · REST API reference · AI agents (MCP) · Formula guide for AI agents