The spreadsheet framework built for agents.
Describe the model you need in natural language — your coding agent writes the source. open-sheet owns the cell addressing, the formula references, the recalculation and the export. No A1 address is ever authored by hand.
Agents write excellent analysis and terrible spreadsheets.
The reason is specific: =SUM(B2:B13). Cell addresses are the one thing an agent cannot hold onto. It miscounts the header row. It forgets the total moved when the data grew. It writes a reference to B7, inserts a quarter above it, and now every formula in the file is quietly wrong. Not crashed. Wrong, which is worse.
So open-sheet takes the addresses away. Add a quarter to the data and press F3 — the column definitions below it do not change, and every address in the file moves anyway.
const quarters = [
{ quarter: 'Q1', revenue: 12_400_000, cogs: 5_100_000 },
{ quarter: 'Q2', revenue: 13_900_000, cogs: 5_600_000 },
{ quarter: 'Q3', revenue: 15_200_000, cogs: 6_050_000 },
{ quarter: 'Q4', revenue: 18_650_000, cogs: 7_100_000 },
]
const quarters = [
{ quarter: 'Q1', revenue: 12_400_000, cogs: 5_100_000 },
{ quarter: 'Q2', revenue: 13_900_000, cogs: 5_600_000 },
{ quarter: 'Q3', revenue: 15_200_000, cogs: 6_050_000 },
{ quarter: 'Q4', revenue: 18_650_000, cogs: 7_100_000 },
{ quarter: 'Q5', revenue: 21_300_000, cogs: 7_900_000 },
]
col('quarter', { header: 'Quarter' }),
col('revenue', {
header: 'Revenue',
format: 'currency',
bar: { color: '#0000aa' },
}),
col('cogs', { header: 'COGS', format: 'currency' }),
col('grossProfit', {
header: 'Gross profit',
format: 'currency',
formula: (r) =>
sub(r.cell('revenue'), r.cell('cogs')),
}),
col('margin', {
header: 'Margin',
format: 'percent',
scale: ['#aa0000', '#000000', '#00aa00'],
formula: (r) =>
div(
sub(r.cell('revenue'), r.cell('cogs')),
r.cell('revenue'),
),
}),
col('qoq', {
header: 'QoQ growth',
format: 'percent',
highlight: {
above: 0.15,
color: '#ffff55',
bold: true,
},
formula: (r) =>
r.isFirst
? null
: sub(
div(r.cell('revenue'), r.prev().cell('revenue')),
1,
),
}),
P&L
| Quarter | Revenue | COGS | Gross profit | Margin | QoQ growth |
| Q1 | 12,400,000 | 5,100,000 | 7,300,000 | 58.9% | |
| Q2 | 13,900,000 | 5,600,000 | 8,300,000 | 59.7% | 12.1% |
| Q3 | 15,200,000 | 6,050,000 | 9,150,000 | 60.2% | 9.4% |
| Q4 | 18,650,000 | 7,100,000 | 11,550,000 | 61.9% | 22.7% |
| Total | 60,150,000 | 23,850,000 | 36,300,000 |
P&L
| Quarter | Revenue | COGS | Gross profit | Margin | QoQ growth |
| Q1 | 12,400,000 | 5,100,000 | 7,300,000 | 58.9% | |
| Q2 | 13,900,000 | 5,600,000 | 8,300,000 | 59.7% | 12.1% |
| Q3 | 15,200,000 | 6,050,000 | 9,150,000 | 60.2% | 9.4% |
| Q4 | 18,650,000 | 7,100,000 | 11,550,000 | 61.9% | 22.7% |
| Q5 | 21,300,000 | 7,900,000 | 13,400,000 | 62.9% | 14.2% |
| Total | 81,450,000 | 31,750,000 | 49,700,000 |
| over four quarters | over five | |
|---|---|---|
| total revenue | B6 =SUM(B2:B5) | B7 =SUM(B2:B6) |
| last gross profit | D5 =B5-C5 | D6 =B6-C6 |
| last QoQ growth | F5 =B5/B4-1 | F6 =B6/B5-1 |
The total row moved down one and its range grew. The new quarter’s growth formula points at a row that did not exist a moment ago. Nobody edited a formula.
Gross profit is =B2-C2 in the file, not 7,300,000. Change a number and the whole sheet recalculates — in Excel, in Sheets, in LibreOffice.
Isn’t that what Claude for Excel does?
Different tool, different job. Claude for Excel makes one person faster at making a spreadsheet. open-sheet makes the spreadsheet not need making a second time.
| Claude for Excel | open-sheet | |
|---|---|---|
| Input | a workbook that already exists | a source file |
| In the loop | a person, every time | a person once, at review |
| Reviewing a change | a binary diff | a code review |
| Doing it 500 times | 500 sessions | a loop |
| Needs | Excel, an account, a paid plan | Node. MIT. |
An assistant working inside a spreadsheet still writes =SUM(B2:B13) into a cell. That is a hand-authored address — authored by a model, but hand-authored. Insert a row and it is wrong, and nothing tells you. This is not a question of the model being clever enough: A1 is the only language that medium has.
And when open-sheet cannot compute a cell, it says so. #NOT_EVALUATED, never a plausible-looking number. Anything writing into a live cell produces something number-shaped whether or not it was sure — the failure that costs the most and shows the least.
Where Claude for Excel wins, and it is not close. It writes live formulas too; the difference is never “ours recalculate and theirs do not.” A workbook you did not generate, we cannot read at all. It computes with Excel’s own engine, so its numbers are Excel’s numbers by construction, where we earn that with a cross-engine check on every build. Five hundred-odd functions to our hundred-odd. Pivot tables.
Use that when you have a spreadsheet and want help with it. Use this when the spreadsheet is the output of a pipeline — a monthly board pack, five hundred invoices, a model regenerated whenever the data lands. The two compose, in that order.
What the recipient gets.
- a live model
- The .xlsx holds formulas, not baked numbers. Change an assumption and it recalculates in whatever they opened it with.
- validate
- A dropdown instead of a free-text field, carrying both the prompt and the refusal message. A rejection with no reason is worse than no validation.
- protect
- Unlocks the cells they are meant to fill in and leaves every formula shut. A derived cell is not an input.
- appendable
- Emits an Excel Table, so a row they add is taken into every range that reads it — and the derived columns fill themselves.
- note
- Attaches provenance to the cell itself. Shown on hover, not buried in a footnote nobody reads.
- Headers, footers, page numbers, and page breaks that name blocks rather than row numbers, so they move when the content does.
For your agent.
Scaffold
npx @open-sheet/cli init my-sheets writes a workspace carrying five skills — how to author, how blocks are placed, how formats work, what the recipient can do with the file, and how it prints.
Ask
/create-sheet and describe the model. The skill opens by confirming the material and refusing to invent numbers, which is the failure this medium punishes hardest.
Inspect
Click any cell in the viewer for its source line, its resolved formula and its computed value — or leave a comment for the agent to apply.
Automate
An MCP server over Streamable HTTP exposes the same operations the browser uses, so a stale write is refused with a 409 rather than silently overwriting somebody.
Checked against a real spreadsheet, every build.
Our evaluator and Excel have to agree about what a formula means, or the number in the viewer is a guess. Every whitelisted function is compiled into a fixture, exported without cached results, recalculated by LibreOffice, and diffed against what we computed. A function joins the whitelist only when a second engine agrees.
That check is also why SORT is implemented here rather than taken from a library: the library’s compares as strings, which is indistinguishable from correct until your numbers stop having the same number of digits.