Skip to content

A1 Excel workbooks, in the browser, under your rules

Spreadsheets that know the rules.

Ruled opens real Excel files in your app and checks every cell against a schema you write, including async checks that call your own services as people type. Then it saves the .xlsx back with its charts, pivot tables and formulas intact.

price-list.xlsxLoads as you scroll to it
Live. This is the editor checking a price list against its schema. Type SKU-1013 under the last SKU: the pretend catalogue service marks it discontinued half a second later. Type SKU-2002 and the row fills itself in.

Why it’s different

C1 · Schemas

A schema is data, with code where you need it.

Everything but the hooks is plain JSON: generate it from a workbook, refine it in the editor’s schema designer, and keep it with your app. The hooks are ordinary functions, sync or async. Each gets an AbortSignal, so a check the user has typed past stops calling your service.

  • Types, bounds, decimals, patterns, lists, uniqueness and required fields
  • Tables down rows or across columns, fixed or growing, and single cells
  • Rules across records, sheets and the whole workbook
  • Messages in each language the editor speaks
How schemas work
price-list.ts
import { defineSchema } from '@ruled/sheet';
import { lookupSku } from './services';
export const priceList = defineSchema({
id: 'docs.price-list',
name: 'Price list',
sheets: [
{
name: 'Prices',
tables: [
{
key: 'prices',
at: 'A1',
records: { min: 1, max: 500 },
fields: [
{
key: 'sku',
label: 'SKU',
type: 'text',
required: true,
unique: true,
pattern: '^SKU-\\d{4}$',
patternHint: 'like SKU-1234',
// An async check against the catalogue service. The editor shows the cell
// as being checked, aborts the call if the value changes again, and re-runs
// it only when this cell's value changes.
validate: async (value, { signal }) => {
const item = await lookupSku(String(value), signal);
if (item?.discontinued) return { message: 'The catalogue lists this product as discontinued.', severity: 'warning' };
},
},
{ key: 'description', label: 'Description', type: 'text', required: true },
{ key: 'category', label: 'Category', type: 'enum', values: ['Hardware', 'Software', 'Services'], required: true },
{ key: 'price', label: 'Unit price', type: 'number', required: true, min: 0, decimals: 2, formula: 'forbid' },
{ key: 'priceIncTax', label: 'Price inc. tax', type: 'number', formula: 'require', description: 'Unit price plus 20% tax.' },
{ key: 'from', label: 'Effective from', type: 'date', required: true, min: '2020-01-01' },
{ key: 'active', label: 'Active', type: 'boolean' },
],
},
],
},
],
// Business logic after each edit: a new SKU fills in its description and category.
onChange: async (ctx) => {
for (const change of ctx.changes) {
if (change.field !== 'sku' || typeof change.after !== 'string' || change.record === undefined) continue;
const record = ctx.sheet(change.sheet)?.table('prices')?.records[change.record];
if (!record || record.values.description) continue;
const item = await lookupSku(change.after, ctx.signal);
if (!item) continue;
const at = { sheet: change.sheet, table: 'prices', record: change.record };
ctx.set({ ...at, field: 'description' }, item.description);
ctx.set({ ...at, field: 'category' }, item.category);
}
},
});

D1 · Anywhere

One editor, in any app.

A React component, or a <workbook-editor> element for everything else. The workbook lives in a web worker, so the page stays quick with tens of thousands of formulas.

import { useWorkbookEditor, WorkbookEditor } from '@ruled/sheet';
import '@ruled/sheet/style.css';
import { priceList } from './schemas';
export function Review() {
const editor = useWorkbookEditor({
schemas: [priceList],
onSave: async (file) => upload(file.bytes, file.values),
});
return editor && <WorkbookEditor editor={editor} />;
}

E1 · Excel in, Excel out

It opens the file people already have, and gives it back.

Formulas
Calculated live by its own engine as cells change, dynamic arrays and LAMBDA included, and checked against the values Excel saved.
Charts
Drawn from the current cells, and inserted, moved, resized, restyled and deleted as in Excel.
Pivot tables
Worked out again as their source changes, with Excel’s field list, filters and Show Values As.
Formatting
Number formats, fonts, fills, borders, conditional formats, merged, hidden and grouped cells, frozen panes.
Data
Sorting, filters, data validation, names, hyperlinks, notes and threaded comments.
Everything else
Kept as it was: macros (or removed, as you choose), pictures, slicers, and parts it doesn’t draw.

F1 · For coding agents

Docs your coding agent can read.

Every page is available as Markdown, gathered in llms.txt and llms-full.txt. An installable skill teaches Claude Code, Cursor and other agents how to embed the editor and write schemas and hooks.

Set up your agent

Try it on a workbook of your own.

The demo opens any .xlsx or CSV file, with the sample workbooks and schemas from these pages. Nothing leaves your browser.