cells

Data and its logic, in one plain-text file.

A .cell file is a table that says what its columns are, computes what it can, and knows the files it points at. Plain text, checked by every reader.

THE FILE ON THAT SCREEN

This is a cell. Everything it does is in its text.

money.cell
---
accounts = ```cells
account:text!
bank
rent
food
coffee
```
---
date:date,amount:EUR>0,from:accounts.account,to:accounts.account,vat:EUR=amount * 21 / 121
2026-11-01,950.00,bank,rent,
2026-11-03,42.50,bank,food,
2026-11-05,3.80,bank,coffee,

01

A CSV knows nothing. Every value is a string that hopes.

96.7x reads as fine, because nothing in the file says what cost is.

groceries.csv
what Cells reads
itemcostqtypaid
1fuel84.201yes
2groceries96.7x2no
3fuel12.000maybe

every cell holds its type

02

Say what each column is. In the header, where it cannot drift.

The schema is the first line of the file, so every reader checks the same rules and names what breaks them.

try Fix the cells: 96.75, 1, no.

groceries.cell
what Cells reads
itemcostqtypaid
1fuel84.201yes
2groceries96.7x2no
3fuel12.000maybe
  • row 0, item: "fuel" is the key of row 2 too
  • row 1, cost: "96.7x" is not a number
  • row 2, item: "fuel" is the key of row 0 too
  • row 2, qty: "0" is below 1
  • row 2, paid: "maybe" is not yes or no

03

Columns that compute. The rule beside the column it fills.

The field stays empty and the header holds the formula, so a value is never stored twice and never stale.

try Change a cost and watch total follow.

groceries.cell
what Cells reads
itemcostqtytotal
1fuel84.20184.20
2groceries96.752193.50
3coffee2.1036.30

every cell holds its type

04

A cell can hold its own. By name, never by coordinate.

There is no B7 to point at the wrong row after a line is inserted by hand: a formula names what it reads.

try Write `=96.75 / 4` instead.

groceries.cell
what Cells reads
itemcostqtytotal
1fuel84.20184.20
2dinner for three32.25396.75

every cell holds its type

05

Files that know each other. A folder of them is a database.

A column typed by another file's key must name a row that exists there, so a typo is caught the moment it is written.

try Fix fodo, or add it to accounts.

what Cells readsmoney.cell with its imports
dateamountfromtovat
12026-11-01950.00bankrent164.88
22026-11-0342.50bankfodo7.38
32026-11-042400.00salarybank416.53
  • row 1, to: "fodo" is no key of accounts

06

Data that fetches itself. A script fills a table, and the file keeps what it got.

Opening a file never runs anything: you see the rows its last run stored, and the run is yours to start.

rates.cell
eurusd, as storedlast run 12
dateraterun
12026-11-030.92879
22026-11-040.93129
32026-11-050.930612
its runsrecorded
runatoutcomeerror
192026-11-04T17:00:03Zok
2122026-11-05T17:00:04Zok
rates.cell, readeur from each day's rate
dateusdnoteeur
12026-11-03180.00two nights167.17
22026-11-0542.50dinner39.55

These runs are recorded: this page runs no script. cells run rates.cell runs it, with Bun, when you ask.

07

Ask the whole folder. Typed tables answer questions.

Every file is a table whose columns are already numbers, dates and references, so a question is a query, never a parse.

every money file, by where it wentdirection: cells sql
$ cells sql . "select a.kind, sum(m.amount) from money m
    join accounts a on m.to = a.account group by a.kind"
kindtotal
1expenses1236.80
2assets2400.00

Answered here from the engine's typed columns of home/money.cell and travel/money.cell. cells sql, DuckDB over the same columns, is what comes next.

AND NOT

Why not what exists. Each is good at something else.

CSV with a JSON schema beside it
two files that drift: the schema says one thing, the file another, and nothing notices
the types are the header: one file, read and checked as one
Parquet
typed, fast, and a binary: no keys, no references, nothing you can read, diff in git or fix by hand
text you read and diff, keys and references checked; Parquet stays a good export
Oils' TSV8
types in the file for shell pipes; the spec is syntax only, so int, float or decimal is each reader's guess
what a value means is the spec, money exact at its cents, the same in every reader
a spreadsheet
formulas point at B7, which moves when a row is inserted; macros run when a file is opened
formulas name columns and keys; opening a file runs nothing

WHERE IT STANDS

Built

  • The spec, and a conformance suite every reader runs.
  • The engine in Rust, passing all of it natively and as WebAssembly: what runs on this page.
  • Formulas in headers and cells, tables written inline, imports pinned by hash, references checked across files.
  • cells check, show, pin, and run with Bun.
  • The table you saw, on the web and on Apple platforms.

Being built, then next

  • cells lsp, being built now: the problems you saw on this page, in your editor, with hover, completion, go to and rename. Helix first.
  • A daemon that keeps the schedules and serves every app.
  • cells sql, DuckDB over the typed columns of a folder.
  • Charts from the types, with no setup.