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.
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
Schemas, not settings
Describe the workbooks you expect: sheets, tables, fields, types, bounds, patterns and rules across fields. A file is matched to its schema as it opens, and every cell is checked as it is typed.
B2Async checks that call your services
A field, table, sheet or workbook check can await your API. The editor shows the cells being checked, abandons calls the user has typed past, times out slow ones, and won’t save past an error.
B3Business logic in the sheet
onChange runs after each edit and can fill cells from your systems, as part of the same undo step. beforeSave has the last word before a file leaves.
B4The file survives
Formulas recalculate live; charts and pivot tables redraw and can be changed. What the editor can’t show it keeps, and a workbook nobody changed saves back byte for byte.
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
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); } },});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.
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.