Typed-xlsx
Formulas

Formula Reference

Complete reference for the formula DSL — operands, conditions, functions, group aggregates, summary formulas, and scope rules.

This page is the exhaustive reference for the formula DSL. For a guided introduction with worked examples, see Formula Columns. For the logical-row vs physical-row mental model, see Row Model.

Entry points

Every formula column callback receives { row, fx }:

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
qty
: number;
price
: number }>()
.
column
("qty", {
accessor
: "qty" })
.
column
("price", {
accessor
: "price" })
.
column
("total", {
formula
: ({
row
,
fx
}) => {
return
row
.
ref
("qty").
mul
(
row
.
ref
("price"));
}, }) .
build
();
ParameterTypeDescription
rowFormulaRowContextReferences other columns and group aggregates
fxFormulaFunctionsSpreadsheet functions (ROUND, IF, MIN, MAX, …)

row — row context

row.ref(columnId)

References the value of another column in the same row. Returns a FormulaOperand.

The column ID is type-checked. Referencing a non-existent column or a column declared after the current one is a TypeScript error.

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
units
: number;
rate
: number }>()
.
column
("units", {
accessor
: "units" })
.
column
("rate", {
accessor
: "rate" })
.
column
("cost", {
formula
: ({
row
}) =>
row
.
ref
("units").
mul
(
row
.
ref
("rate")),
}) .
build
();

Primitive literals

String, number, and boolean primitives can be passed directly into formula operations. They are embedded as Excel literals automatically.

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
amount
: number }>()
.
column
("amount", {
accessor
: "amount" })
.
column
("withVat", {
formula
: ({
row
}) =>
row
.
ref
("amount").
mul
(1.2),
}) .
build
();

row.if(condition, whenTrue, whenFalse)

Inline IF — equivalent to fx.if(). Returns a FormulaOperand. Available directly on row for convenience.

row.group(groupId)

References a group of columns declared with .group(). Returns a FormulaGroupContext. See group aggregates below.


FormulaOperand — arithmetic and comparison

A FormulaOperand is returned by row.ref() and most fx.* functions. It exposes arithmetic operators, comparison operators, and unary functions chainably.

Arithmetic

MethodExcel equivalentExample
.add(right)A + Brow.ref("a").add(row.ref("b"))
.sub(right)A - Brow.ref("a").sub(1)
.mul(right)A * Brow.ref("qty").mul(row.ref("price"))
.div(right)A / Brow.ref("total").div(row.ref("count"))

right can be a FormulaOperand, a FormulaExpr, or a primitive (string | number | boolean). Primitives are automatically wrapped in a literal.

Unary

MethodExcel equivalentExample
.abs()ABS(A)row.ref("delta").abs()
.round(n?)ROUND(A, n)row.ref("rate").round(4)

n defaults to 0.

Comparison — returns FormulaCondition

MethodExcel equivalentExample
.eq(right)A = Brow.ref("status").eq("active")
.neq(right)A <> Brow.ref("code").neq(0)
.gt(right)A > Brow.ref("score").gt(90)
.gte(right)A >= Brow.ref("mrr").gte(1000)
.lt(right)A < Brow.ref("age").lt(30)
.lte(right)A <= Brow.ref("risk").lte(0.5)

FormulaCondition — boolean logic

FormulaCondition is returned by comparison operators. It can be passed directly to fx.if(), or chained.

Chaining

MethodExcel equivalentExample
.and(right)AND(A, B)row.ref("a").gt(0).and(row.ref("b").lt(100))
.or(right)OR(A, B)row.ref("tier").eq("A").or(row.ref("mrr").gte(5000))
.not()NOT(A)row.ref("active").eq(true).not()

Conditions can be chained multiple times:

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
score
: number;
tier
: string;
active
: boolean }>()
.
column
("score", {
accessor
: "score" })
.
column
("tier", {
accessor
: "tier" })
.
column
("active", {
accessor
: "active" })
.
column
("eligible", {
formula
: ({
row
,
fx
}) =>
fx
.
if
(
row
.
ref
("active").
eq
(true).
and
(
row
.
ref
("score").
gte
(80)).
and
(
row
.
ref
("tier").
eq
("premium")),
"YES", "NO", ), }) .
build
();

fx — formula functions

fx.if(condition, whenTrue, whenFalse)

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
revenue
: number;
target
: number }>()
.
column
("revenue", {
accessor
: "revenue" })
.
column
("target", {
accessor
: "target" })
.
column
("status", {
formula
: ({
row
,
fx
}) =>
fx
.
if
(
row
.
ref
("revenue").
gte
(
row
.
ref
("target")), "ON TRACK", "BEHIND"),
}) .
build
();

Nested if calls create IF(…, …, IF(…)) chains:

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
score
: number }>()
.
column
("score", {
accessor
: "score" })
.
column
("grade", {
formula
: ({
row
,
fx
}) =>
fx
.
if
(
row
.
ref
("score").
gte
(90),
"A",
fx
.
if
(
row
.
ref
("score").
gte
(75), "B",
fx
.
if
(
row
.
ref
("score").
gte
(60), "C", "F")),
), }) .
build
();

fx.round(value, decimals?)

Wraps a value in ROUND(value, n). decimals defaults to 0.

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
amount
: number;
rate
: number }>()
.
column
("amount", {
accessor
: "amount" })
.
column
("rate", {
accessor
: "rate" })
.
column
("result", {
formula
: ({
row
,
fx
}) =>
fx
.
round
(
row
.
ref
("amount").
mul
(
row
.
ref
("rate")), 2),
}) .
build
();

fx.abs(value)

Wraps a value in ABS(value).

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
baseline
: number;
current
: number }>()
.
column
("baseline", {
accessor
: "baseline" })
.
column
("current", {
accessor
: "current" })
.
column
("deviation", {
formula
: ({
row
,
fx
}) =>
fx
.
abs
(
row
.
ref
("current").
sub
(
row
.
ref
("baseline"))),
}) .
build
();

fx.min(...values) and fx.max(...values)

Accept any number of operands, literals, or conditions.

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
bid
: number;
ask
: number;
mid
: number }>()
.
column
("bid", {
accessor
: "bid" })
.
column
("ask", {
accessor
: "ask" })
.
column
("mid", {
accessor
: "mid" })
.
column
("spread", {
formula
: ({
row
,
fx
}) =>
fx
.
max
(
row
.
ref
("ask").
sub
(
row
.
ref
("bid")), 0),
}) .
build
();

fx.and(...conditions) and fx.or(...conditions)

Variadic AND / OR — accept multiple FormulaCondition or FormulaExpr values:

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
a
: number;
b
: number;
c
: number }>()
.
column
("a", {
accessor
: "a" })
.
column
("b", {
accessor
: "b" })
.
column
("c", {
accessor
: "c" })
.
column
("allPositive", {
formula
: ({
row
,
fx
}) =>
fx
.
if
(
fx
.
and
(
row
.
ref
("a").
gt
(0),
row
.
ref
("b").
gt
(0),
row
.
ref
("c").
gt
(0)), "YES", "NO"),
}) .
build
();

fx.not(condition)

Wraps a condition in NOT(…):

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
createExcelSchema
<{
blocked
: number }>()
.
column
("blocked", {
accessor
: "blocked" })
.
column
("allowed", {
formula
: ({
row
,
fx
}) =>
fx
.
if
(
fx
.
not
(
row
.
ref
("blocked").
eq
(1)), "ALLOWED", "BLOCKED"),
}) .
build
();

Group aggregates

When columns are declared inside a .group(), a formula column declared after the group can aggregate all of its columns using row.group(groupId):

import { 
createExcelSchema
} from "@chronicstone/typed-xlsx";
type
SalesRow
= {
q1
: number;
q2
: number;
q3
: number;
q4
: number };
const
schema
=
createExcelSchema
<
SalesRow
>()
.
group
("quarters", (
g
) => {
g
.
column
("q1", {
header
: "Q1",
accessor
: "q1" });
g
.
column
("q2", {
header
: "Q2",
accessor
: "q2" });
g
.
column
("q3", {
header
: "Q3",
accessor
: "q3" });
g
.
column
("q4", {
header
: "Q4",
accessor
: "q4" });
}) .
column
("annual", {
header
: "Annual",
formula
: ({
row
}) =>
row
.
group
("quarters").
sum
(),
style
: {
font
: {
bold
: true } },
}) .
column
("avgQuarter", {
header
: "Avg / Quarter",
formula
: ({
row
,
fx
}) =>
fx
.
round
(
row
.
group
("quarters").
average
(), 0),
}) .
build
();

Available group aggregates

MethodExcel output
.sum()Sum of group columns in this row
.average()Average of group columns in this row
.min()Minimum of group columns in this row
.max()Maximum of group columns in this row
.count()Count of non-empty group columns

Group aggregates work identically in report mode and excel-table mode — only the emitted cell reference style differs.


Summary formulas

Summary rows have their own formula context ({ column, fx }) — see Summary Formulas for the full reference.


Scope rules and mode output

Scope rules (predecessor ordering, group scope, runtime errors) and how mode affects the emitted formula string (A1 vs structured references) are covered in Scope and Modes.


Type reference

TypeDescription
FormulaOperandArithmetic result, supports .add/sub/mul/div, comparisons, .abs/.round
FormulaConditionBoolean result, supports .and/.or/.not, passable to fx.if
FormulaValuestring | number | boolean | FormulaOperand | FormulaExpr — accepted everywhere a value is expected
FormulaConditionValueFormulaCondition | FormulaExpr — accepted in boolean positions
FormulaRowContext{ ref, group, literal, if } — the row parameter
FormulaFunctions{ if, round, abs, min, max, and, or, not } — the fx parameter
FormulaGroupContext{ sum, average, min, max, count } — returned by row.group()
Copyright © 2026 Cyprien Thao. Released under the MIT License.