Loss-aware by design
Anything the model can't yet represent — a chart, a pivot cache, a rare style bit — is reproduced verbatim or reported. Never silently discarded.
OpenCalc reads and writes real .xlsx, keeps the workbook in a
normalized editable model, recalculates it, and renders a virtualized grid —
the same Rust core on the desktop, in the browser through WebAssembly, and
headless on a server.
Nothing is uploaded — the engine runs on your machine. Apache-2.0.
Why OpenCalc
Every layer is testable in isolation, and every byte it writes is reproducible. That is what makes it safe to embed in something you ship.
Anything the model can't yet represent — a chart, a pivot cache, a rare style bit — is reproduced verbatim or reported. Never silently discarded.
The same input and engine version produce the same model, values, layout, and bytes — so both calc and render are regression-tested against golden files.
No mandatory DOM, server, UI framework, or collaboration provider. Rust hosts, wasm32, desktop, and headless services all consume the same core.
A canvas grid with real selection, drag-fill, merges, freeze panes, number formats and keyboard navigation — drawn from the engine's own layout, not the DOM.
Packages are admitted under explicit entry, path, size, expansion and resource limits. No macro execution, no automatic external fetches.
A layered crate DAG from model and parser up to layout and render, unsafe forbidden, Apache-2.0. Read every line before you depend on it.
Shipping now
A verified list, not a roadmap — everything here is exercised by the editor you just used. What is still pending is in the roadmap below.
Reads and writes .xlsx · imports and exports CSV, TSV, PSV · formatting, tables, charts, validation, protection, print setup and defined names survive the round-trip — and anything not yet modelled is carried through byte for byte rather than dropped.
Under the hood
Parsing sits below evaluation, so import and editing use the formula AST without pulling in the calc engine at all.
A Tauri host where the whole engine — including calc — runs as native Rust at full speed.
The same core compiled to wasm32, driving a canvas grid entirely client-side. The editor above is the proof.
No UI at all — batch convert, recalculate, and rasterize a viewport to a deterministic PNG on a server.
// One session composes every layer: open, edit, recalc, render, save.
let mut session = WorkbookSession::open(bytes)?;
session.edit(Operation::SetCell { sheet: 0, at: a1("B2"), cell })?;
let png = session.render_png(0, &viewport)?; // virtualized grid → PNG
let xlsx = session.save()?; // deterministic bytes
Embed it
One custom element, one line of markup, and your users get the editor
above — .xlsx in and out, 347 functions, pivot tables and
charts, all client-side. It works in React, Next.js, Vue, Svelte or
plain HTML.
The element and, if you use React, its wrapper. No peer dependency on a UI framework.
# the editor as a custom element
npm install @opencalc/sheet
The WebAssembly binary is copied into your build and served from your origin — never from a CDN we run.
# package.json → postinstall
opencalc-assets ./public/opencalc
Give it a height and it boots. Open a workbook, get bytes back, listen for changes.
import "@opencalc/sheet"; <opencalc-sheet style="height:600px">
const sheet = document.querySelector("opencalc-sheet");
sheet.theme({ light: { accentColor: "#7c3aed" } }); // CSS tokens, scoped by a shadow root
sheet.chrome({ statusbar: false }); // hide whole regions…
sheet.commands({ hidden: ["file.open"] }); // …or single controls, by id
await sheet.configure({ access: "view" }); // "edit" | "view" | "preview"
await sheet.open(bytes, "budget.xlsx");
sheet.on("cellsChanged", (e) => {
if (e.source !== "api") persist(await sheet.save()); // deterministic .xlsx
});
Try it
Co-editing is not a feature you should have to take on trust. Two containers, one command, and you are sharing a link with a colleague and typing into the same sheet.
The editor, a collaboration server and a reference host. Nothing else to install.
# change OPENCALC_SHARED_SECRET in .env
cp .env.example .env
docker compose up
Open localhost:8080, make a spreadsheet, press Share, send the link. You will both see each other's cursors and each other's edits.
# and a cluster, when one node is not enough
docker compose -f docker-compose.cluster.yml up
Download gives you an .xlsx with what you both typed — the server hands the finished bytes back to the host rather than keeping them.
The images are published, so that pulls rather than builds:
docker pull casualoffice/calc # the collaboration server docker pull casualoffice/calc-host # the editor, and your documents docker pull casualoffice/wopi # the WOPI adapter, if you need one
Each is linux/amd64 and linux/arm64, and each ships an
SBOM and a build-provenance attestation naming the repository, workflow and
commit it came from — so what you pull can be traced back to what is here.
Add --build to any compose command to build from your own checkout
instead.
The two containers are the integration boundary, not a demo artefact. The collaboration server is what OpenCalc supplies — ordering, presence, the socket. The host is what your product supplies — documents, identity, tokens, saving. It holds no per-document state and cannot mint tokens, so it is told, per join, by a party that already knows. That is four endpoints and a signature, and the host you just ran is the worked example of the one you would write.
The self-hosting guide covers reverse proxies, TLS, every setting, monitoring, backup and what the demo defaults are not safe for.
The trade-off everyone else makes
Most options force a compromise: a suite you can't embed, a converter that quietly drops what it doesn't understand, or a web grid that treats the DOM as the source of truth and calculates on a server.
Categories, not products — Excel and LibreOffice Calc remain our semantics and fidelity oracles, and we study IronCalc, calamine and Univer openly.
Honest status
Delivered in capability-gated phases. The workbook has to be read, modeled, preserved, written back, laid out and rendered before a single cell is evaluated.
No sign-up, no upload, no build step. Bring an .xlsx and watch it round-trip — then put the same thing in your own product.
Apache-2.0, CI-gated, fuzzed and benchmarked. Fidelity bugs with a fixture attached are the most valuable thing you can send.