A database you can keep in a folder.
.table is an open file format for the tables people build in Airtable,
Notion or a spreadsheet: typed columns, linked tables, formulas, and
views such as boards and calendars.
What's different is where it lives. A .table is a file on your own
disk, not a workspace on someone else's server. Inside it is plain text:
a schema, and one line per row. Any tool can read it, git can track it,
and you never need the app that made it to get your data back.
It is for people who work with information, not only for developers. Opened in an app, it looks and edits like the tools you already know.
Try the demo in your browser: nothing is uploaded, and edits stay in that browser.
Status. The format is frozen at
formatVersion: 1, and changes from here only add to it. The library and the apps in this repository are a reference implementation: they work and are tested, they are not on npm yet, and their API may change before 1.0.
Tables move between tools badly. CSV loses the types. .xlsx and
.numbers are binary and belong to one app. Airtable, Notion and Google
Tables keep the data on their servers. None of them is open, typed,
comparable line by line and self-contained at once.
.table aims to be all four, and to hold what a real Airtable base
holds: several linked tables, attachments, a long note on any row, and
every saved view.
A .table is a folder that behaves as one file, as a macOS .app does.
crm.table/
├── meta.json the file's title, and the order of its tables
├── tables/
│ ├── companies/
│ │ ├── schema.json the fields: names, types, constraints, formulas
│ │ ├── rows/ the rows, one JSON object per line
│ │ │ └── 0001.ndjson a new file each ~25 MB: 0002, 0003…
│ │ ├── views.json saved views: table, board, gallery, list, calendar
│ │ ├── bodies/ a markdown document for any row that has one
│ │ └── attachments/ files that rows refer to
│ └── deals/…
└── index.sqlite a cache for querying and search; safe to delete
Only schema.json and rows/ are required. A row is a line:
{"id":"p1","title":"Workspace v1","status":"active","owner":"leslie","budget":80000,"launched":"2026-01-15"}and a field is an entry in the schema:
{ "name": "budget", "type": "number", "format": "currency:USD" }The whole format is in docs/SPEC.md, and the reasons behind it in docs/DECISIONS.md.
- Typed fields. Text, numbers, booleans, dates, times, durations, lists, objects and places. Each type has one exact encoding, so two readers of the same file see the same values.
- Constraints. Required, unique, a set of choices, a range, a length, a pattern.
- Relations. A field can point at rows in another table of the same file.
- Formulas. Typed as in a spreadsheet (
=round(budget / 12, 0)) and stored in one canonical form. A formula can read other rows, follow a relation to look a value up, and total the rows that point back. Its result is worked out when the table is read and never stored, so it can't go stale. - Views. Any number of saved views of a table, each with its own
filter, sort, grouping, columns and totals, laid out as a table, a
board, a gallery, a list or a calendar. A Sheet view letters its
columns and numbers its rows, so a formula can refer to
B7. - A document per row. A row can carry a long markdown body, kept as its own file.
- Attachments. Files stored beside the rows that refer to them.
- History. An optional append-only log of edits, which the apps write as you edit.
A .table can also be a single file, named .table all the same,
which is how it's sent or kept by anyone who wants one file. A
spreadsheet opens as a new table from CSV.
@workspace.sh/table-core is the reference library, in TypeScript. It
runs in Node, in browsers and in React Native.
import { parseBundle } from "@workspace.sh/table-core/parser";
import { applyView, validate } from "@workspace.sh/table-core";
const file = await parseBundle("./fixtures/projects.table");
const projects = file.tables.projects!;
// Check the rows against the schema.
const problems = validate(projects.schema, projects.rows);
// The rows a saved view shows: filtered, sorted, formulas worked out.
const view = projects.views.find((v) => v.name === "Active by owner")!;
const rows = applyView(projects, view);docs/INTEGRATION.md is the guide to using it from an app, including writing, archives and CSV. It also describes the smallest reader worth writing in another language.
Each app opens the same files and shows the same views. They exist to prove the format and the libraries, and to be read as examples. What each one does with every part of the spec, and what's still to come, is in docs/SUPPORT.md.
| Platform | Where | Built with |
|---|---|---|
| Web | apps/web |
React and react-strict-dom. This is the live demo. |
| iOS | apps/mobile |
Expo and React Native, with the system's own controls; the views inside them are react-strict-dom. iOS 26 and later. |
| macOS | apps/desktop |
react-native-macos, in a native window with a sidebar, toolbar and inspector; the views inside it are react-strict-dom. |
| Linux | apps/linux |
GTK 4 and libadwaita, through GTKX. |
| Android | apps/mobile |
Not yet: the same code as iOS, with Material controls, but it hasn't been run on a device, and it likely stops at launch for want of a file module iOS has. What's missing is listed in #429. |
The web, iOS, macOS and Android apps draw their views with
@workspace.sh/table-ui, written once for all four in react-strict-dom,
which draws HTML elements on the web and React Native's components
elsewhere. Linux draws the same
views as GTK widgets with @workspace.sh/table-gtk. Where the platforms
do differ is listed in docs/PLATFORMS.md.
A table of 50,000 rows or more is read through index.sqlite and not
held in memory, and an app loads only the rows near the screen. The
web, Linux and iOS apps open tables of a million rows this way. What was
measured, and how, is in docs/LARGE-TABLES.md.
Needs Node 22.20.0 (.nvmrc).
npm install
npm run web:dev # the web app, at http://localhost:5173
npm run core:test # the format library's testsThe other apps need their platform's tools, and each has its own notes: iOS and Android, macOS, Linux. Every command is listed in docs/DEVELOPING.md.
packages/
core/ the format: read, write, validate, query, formulas, index
ui/ the views, for web, iOS, Android and macOS
gtk/ the same views for GTK on Linux
app/ what an app around the views does: state, undo, settings, archives
conformance/ formula test cases from OpenFormula, and a runner
table-fixtures/ the fixtures, built in for the apps that can't read the folder
glass-bar/, file-browser/ parts of the iOS app's interface
apps/ web, mobile, desktop, linux
fixtures/ the .table files every app and test uses (a link into the workspace-fixtures submodule)
docs/ the specification and everything behind it
| SPEC | The format, in full. |
| SUPPORT | What each app does with every part of the spec, and what is still to come. |
| DECISIONS | Each choice in the format, numbered, with its reasons. |
| INTEGRATION | Using .table from your own app. |
| PRIOR-ART | The formats this was compared against. |
| ARCHITECTURE | How the libraries and apps are put together. |
| PLATFORMS | Where the views fork by platform, and how to replace a control. |
| APP-STATE | The one reducer every app shares. |
| LARGE-TABLES | Measurements from 10,000 to 1,000,000 rows. |
| BENCHMARKING | How to measure, so numbers can be compared. |
| STORAGE-AND-SYNC | How the files relate to a database and to sync. |
| PERMISSIONS | A design for access control under sync. Not implemented. |
| VERSIONING | Version and build numbers. |
| DEVELOPING | Every command. |
| SCREENSHOTS | The web app, showing the sample files. |
The format is stable. It was frozen at formatVersion: 1 in July
2026, and anything added since is optional and ignored by a reader that
doesn't know it.
The library and apps are in active development. What is being worked on, and what is left, is on the project board.
Not in the format, on purpose: sync and real-time collaboration. A
.table is a file, and moving it between people and devices is the job
of the app that holds it. The reasoning is in
DECISIONS (D14 and D17) and
STORAGE-AND-SYNC. Workspace's own sync and
permissions are specified in the
Workspace Protocol;
PERMISSIONS is how they apply to a .table.
.table is developed as part of
Workspace, and is meant to be useful
without it.
MIT.
