Help Center · Reference
76 builtins, and a sandbox that stops.
A script column computes each row’s value from a few lines of code, for the cases a formula cannot express. Add one from Insert → Script and write it in the column editor with a live preview. Scripts run with no network access, no DOM and no eval, under a step budget — a runaway loop ends rather than hangs.
The language.
Return a value
return cell("Price") * cell("Qty");A script column's value is whatever you return. Every script should end by returning something.
Read other columns
let name = cell("First Name") + " " + cell("Last Name");
return name;cell("Column Name") reads another column in the same row. Names are case-insensitive.
Variables
let subtotal = 100;
let tax = subtotal * 0.07;
return subtotal + tax;Declare a variable with let, then reassign it with =.
Conditionals
let score = cell("Score");
if (score >= 90) { return "A"; }
else if (score >= 80) { return "B"; }
return "C";if / else if / else branch on a true/false condition.
Loops
let total = 0;
for (let i = 1; i <= 10; i = i + 1) {
total = total + i;
}
return total;for and while repeat a block. A step limit stops runaway loops automatically.
Operators
return "Total: " & (cell("A") + cell("B")) ^ 2;Arithmetic + - * / % and ^ (power), comparisons == != <> < > <= >= , and logic && || ! . Text joins with & (always) or + (when a side is already text). ^ is worked out before * / , which are worked out before + - , which come before & ; a comparison is last. -2 ^ 2 is 4, and 2 ^ 3 ^ 2 is 512. Text comparisons ignore capitals, so "Done" == "done" is true — the same rule sheets, forms and automations use. Note that = on its own means ASSIGNMENT here, not equality: write == to compare.
Comments
// Margin as a percentage
return (cell("Price") - cell("Cost")) / cell("Price") * 100;Anything after // on a line is ignored.
Built-in functions.
Row
cell(name)The value of another column in the current row. cell("Price")
ROWID()This row's stable, immutable backend id — unchanged when rows are inserted, sorted, or moved. Useful for reference codes, relation keys, and audit links. return "TASK-" + ROWID();
Math
ROUND(number, [digits])Round to a number of decimal places. ROUND(3.14159, 2)
ABS(number)Absolute value. ABS(-5)
FLOOR(number)Round down to a whole number. FLOOR(4.9)
CEIL(number)Round up to a whole number. CEIL(4.1)
CEILING(number)Round up to a whole number — identical to CEIL. This is the spelling tables, forms and automations use, so a formula moved from any of them works unchanged. CEILING(4.1)
SQRT(number)Square root. SQRT(144)
POW(base, exp)Raise to a power. POW(2, 8)
POWER(base, exp)Raise to a power — identical to POW, and the spelling tables, forms and automations use. The `^` operator is the shorter way to write the same thing: 2 ^ 10 and POWER(2, 10) are both 1024 here, as they are in a sheet. (This entry used to say `^` was not part of this language, which was true until it was added.) POWER(2, 10)
MOD(a, b)Remainder of a divided by b. MOD(10, 3)
MIN(a, b, …)Smallest of the numbers. Note: inside the in-grid Script console a cross-sheet aggregate of the same name claims every call, so this numeric form returns nothing there — AVERAGE is never shadowed. MIN(4, 9, 2)
MAX(a, b, …)Largest of the numbers. Note: inside the in-grid Script console a cross-sheet aggregate of the same name claims every call, so this numeric form returns nothing there — AVERAGE is never shadowed. MAX(4, 9, 2)
SUM(a, b, …)Adds the numbers together. Note: inside the in-grid Script console a cross-sheet aggregate of the same name claims every call, so this numeric form returns nothing there — AVERAGE is never shadowed. SUM(10, 20, 5)
AVERAGE(a, b, …)The mean of the numbers. AVERAGE(2, 4, 6)
AVG(a, b, …)The mean of the numbers — identical to AVERAGE, and the spelling automations use. Note: inside the in-grid Script console a cross-sheet aggregate of the same name claims every call, so this numeric form returns nothing there — AVERAGE is never shadowed. AVG(2, 4, 6)
PRODUCT(a, b, …)Multiplies the numbers together. PRODUCT(2, 3, 4)
COUNT(a, b, …)How many of the arguments are numbers. Note: inside the in-grid Script console a cross-sheet aggregate of the same name claims every call, so this numeric form returns nothing there — AVERAGE is never shadowed. COUNT(1, "x", 3)
COUNTA(a, b, …)How many of the arguments are not blank. COUNTA(1, "", "x")
NUMBER(value)Convert a value to a number. NUMBER("42")
SIGN(number)-1, 0, or 1 for the sign of a number. SIGN(-4)
TRUNC(number)Drop the decimal part (toward zero). TRUNC(4.9)
YEAR(date)The 4-digit year of a date. YEAR(cell("Due"))
MONTH(date)The month (1–12) of a date. MONTH(cell("Due"))
DAY(date)The day of month (1–31) of a date. DAY(cell("Due"))
WEEKDAY(date)Day of week, 1 = Sunday … 7 = Saturday. WEEKDAY(cell("Due"))
DAYSBETWEEN(start, end)Whole days from start to end (negative if end is earlier). DAYSBETWEEN(cell("Start"), TODAY())
DATEADD(date, days)The date a number of days later (negative = earlier). DATEADD(cell("Start"), 30)
ADDMONTHS(date, months)The date a number of months later, clamped to the month end. ADDMONTHS(cell("Start"), 12)
DATE(year, month, day)Build a date (YYYY-MM-DD) from parts. DATE(2026, 7, 21)
TODAY()Today's date as YYYY-MM-DD. TODAY()
ROUNDUP(number, [digits])Round away from zero to the given decimals. ROUNDUP(2.1, 0)
ROUNDDOWN(number, [digits])Round toward zero to the given decimals. ROUNDDOWN(2.9, 0)
LN(number)Natural logarithm (base e). For a base-10 logarithm use LOG(x) or LOG10(x). LN(2.718)
LOG10(number)Base-10 logarithm. Exact for powers of ten, where LOG(x, 10) can land a hair under. LOG10(1000)
EXP(number)e raised to a power. EXP(1)
PI()The constant π. PI()
LOG(number, [base])Logarithm of a number, base 10 unless you give another base — the same meaning Excel and Google Sheets both give it. Refuses a number ≤ 0, and a base ≤ 0 or equal to 1, rather than returning an infinity or a NaN that would spread through the rest of the script. (It used to be a lower-case alias of PRINT, which made the logarithm unreachable; printing is now spelled PRINT only.) LOG(1000)
Text
LEN(text)Number of characters. LEN("hello")
UPPER(text)Upper case. UPPER("hi")
LOWER(text)Lower case. LOWER("HI")
TRIM(text)Remove surrounding spaces. TRIM(" hi ")
LEFT(text, n)First n characters. LEFT("widget", 3)
RIGHT(text, n)Last n characters. RIGHT("widget", 3)
MID(text, start, len)Characters from a position. MID("widget", 2, 3)
CONCAT(a, b, …)Join values into text. CONCAT("A", "-", 1)
TEXT(value)Convert a value to text. TEXT(42)
SUBSTITUTE(text, find, replace)Replace every occurrence of find with replace. SUBSTITUTE("a-b-c", "-", "/")
REPLACE(text, find, replace)Replace the first occurrence of find. REPLACE("a-b-c", "-", "/")
INDEXOF(text, part)1-based position of part in text (0 if not found). INDEXOF("widget", "dg")
PROPER(text)Capitalize the first letter of each word. PROPER("john doe")
REPEAT(text, count)Repeat text a number of times. REPEAT("=", 10)
MASK(text, [keep_last], [mask_char])Hide all but the last few characters — an account number as ************1234. Keeps 4 by default. Asking to keep more than the value holds, or a negative count, never reveals it. MASK(Account)
PADSTART(text, length, [pad])Pad the start of text to a length. PADSTART("7", 3, "0")
PADEND(text, length, [pad])Pad the end of text to a length. PADEND("7", 3, "0")
REGEXMATCH(text, pattern)True if the text matches the regular expression. REGEXMATCH(cell("Email"), "@")
REGEXEXTRACT(text, pattern)The first match (or first capture group) of a regular expression. REGEXEXTRACT(cell("SKU"), "\\d+")
REGEXREPLACE(text, pattern, replacement)Replace every regex match with the replacement. REGEXREPLACE(cell("Phone"), "\\D", "")
SPLITPART(text, delimiter, index)The 1-based part of a delimited string (empty if out of range). SPLITPART(cell("Email"), "@", 2)
JOIN(delimiter, part1, …)Join the non-empty parts with a delimiter (skips blanks). JOIN(" ", cell("First"), cell("Middle"), cell("Last"))
Logic
IF(condition, then, [else])Returns `then` when the condition is true, otherwise `else` (blank if omitted). Only the chosen branch is evaluated. IF(cell("Qty") > 100, "bulk", "standard")
IFS(cond1, val1, cond2, val2, …)Returns the value for the first true condition. IFS(cell("Score") >= 90, "A", cell("Score") >= 80, "B", true, "C")
SWITCH(value, case1, result1, …, [default])Matches value against each case and returns its result; the last lone argument is the default. SWITCH(cell("Tier"), "gold", 0.2, "silver", 0.1, 0)
AND(a, b, …)True only if every argument is true (short-circuits). AND(cell("Paid"), cell("Shipped"))
OR(a, b, …)True if any argument is true (short-circuits). OR(cell("Urgent"), cell("Overdue"))
NOT(value)Inverts a true/false value. NOT(ISBLANK(cell("Email")))
CONTAINS(text, part)True if text contains part (case-insensitive). CONTAINS("widget", "idg")
ISBLANK(value)True if the value is empty. ISBLANK(cell("Notes"))
ISNUMBER(value)True if the value is a number (or a text value that reads as one). IF(ISNUMBER(cell("Qty")), cell("Qty"), 0)
ISTEXT(value)True if the value is non-blank text that isn't a number. ISTEXT(cell("Code"))
STARTSWITH(text, part)True if text starts with part (case-insensitive). STARTSWITH("widget", "wid")
ENDSWITH(text, part)True if text ends with part (case-insensitive). ENDSWITH("widget", "get")
COALESCE(a, b, …)The first value that isn't blank. COALESCE(cell("Nick"), cell("Name"))
ISEMAIL(value)True if the value looks like a valid email address. ISEMAIL(cell("Email"))
ISURL(value)True if the value looks like a valid http(s) URL. ISURL(cell("Website"))
Debug
PRINT(value)Write a value to the Script console and return it unchanged — so it can be dropped into the middle of an expression to see what a step produced. Where no console is listening (an app expression, a script column) it simply returns its argument. return PRINT(cell("Price")) * 2;
For most calculations a formula is quicker — scripts are for multi-step logic. The formula reference is here →
Write one against real rows.
Start free and add a script column to a sheet.