I opened the Univer repo this week expecting a spreadsheet component. The GitHub description called it “The Office Harness for AI Agents” and listed six document types. It took several screens of README before I learned that the open-source build can’t open an .xlsx file.

Anyone who’s tried to embed a spreadsheet knows that gap. The usual options are a data-grid component that looks like Excel but computes nothing, or a hosted embed that lives in someone else’s account. The grids are fine until someone types =SUM(. The embeds are fine until someone asks where the data lives. Neither runs on your server.

What Univer is

Univer is an Apache-2.0 TypeScript SDK from DreamNum for building spreadsheet, document and presentation editors inside your own application. The monorepo ships a core, a canvas render engine, a formula engine and a long tail of feature plugins as @univerjs/* packages, plus curated presets for people who’d rather not pick thirteen packages by hand.

The repo has 22.8k stars and 5,950 commits on dev. Version 1.0.0 reached npm on 23 September 2026, and the current release is 1.3.1. The pitch is one Facade API that drives a workbook identically in the browser and in Node.js, with every capability delivered as a plugin you can swap out or lazy-load.

Why bother

The honest objection is that spreadsheets in web apps are already solved, and the solution is “export a CSV and let them use Excel.” Nobody’s roadmap improves by rebuilding Excel.

That holds until the data has to come back. The round trip flattens formulas to values, drops your validation, and puts the editing outside your permission model. If users will edit formulas anyway, keeping that inside the app is safer. And there’s a case the CSV answer doesn’t touch: generating or recalculating workbooks on a server, where you want exactly the numbers a user would see in the browser.

The part I actually find interesting

“Isomorphic” is an easy word to put in a README. What backs it up here is a rule in the repo’s contributor notes, docs/ISOMORPHIC.md: any feature that has to run on both client and server must be split into at least two plugins. Models, commands, mutations and services go in the logic plugin. Menus, shortcuts, React components and canvas elements go in the UI plugin. Filtering is sheets-filter plus sheets-filter-ui. Anything needing fs or child_process gets a third, server-only plugin.

The sharpest line in that document is that mutations must not read UI state. Mutations are what actually change the document, and if they never touch the UI they can run anywhere. I’d expect the Pro collaboration server and its changeset replay tooling to depend on that, though I can’t see their source. It’s also the basis of the AI angle: an agent calling the Facade from Node goes through the same logic plugins as a user in the browser.

The practical upshot is that headless setup is the browser setup minus the -ui packages. This is the official Node example:

import { LocaleType, mergeLocales, Univer } from '@univerjs/core'
import { FUniver } from '@univerjs/core/facade'
import { UniverDocsPlugin } from '@univerjs/docs'
import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'
import { UniverRenderEnginePlugin } from '@univerjs/engine-render'
import { UniverSheetsPlugin } from '@univerjs/sheets'
import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'
import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'
import SheetsEnUS from '@univerjs/sheets/locale/en-US'
// Facade side-effect imports: each one adds methods to univerAPI
import '@univerjs/engine-formula/facade'
import '@univerjs/sheets/facade'
import '@univerjs/sheets-formula/facade'
import '@univerjs/sheets-numfmt/facade'

const univer = new Univer({
  locale: LocaleType.EN_US,
  locales: { [LocaleType.EN_US]: mergeLocales(SheetsEnUS) },
})

// No UniverUIPlugin, no *-ui packages, no CSS
univer.registerPlugin(UniverRenderEnginePlugin)
univer.registerPlugin(UniverFormulaEnginePlugin)
univer.registerPlugin(UniverDocsPlugin)
univer.registerPlugin(UniverSheetsPlugin)
univer.registerPlugin(UniverSheetsFormulaPlugin)
univer.registerPlugin(UniverSheetsNumfmtPlugin)

const univerAPI = FUniver.newAPI(univer)
univerAPI.createWorkbook({})

I assumed “canvas-based rendering” meant the render engine was strictly a browser concern and would be the first thing you’d strip on a server. It isn’t: the Node example still registers UniverRenderEnginePlugin. My guess is that text measurement and layout live in that package and only the drawing surface is browser-bound, but I haven’t confirmed that in the source.

What setup looks like

The fastest route is preset mode: pnpm add @univerjs/presets @univerjs/preset-sheets-core, import the preset’s CSS, call createUniver with a container ID, and give the page a <div id="app"> with an explicit height. The README’s example uses 100vh. Plugin mode spells the same app out across thirteen packages, six CSS imports and six Facade imports. Verbose, but honest about what you ship.

Headless use needs Node 18.17 or later; hacking on the monorepo needs Node 22.18 and pnpm 11. Browser builds target Chrome 88, Safari 14.1 and Firefox 90, and you’ll need an Intl.Segmenter polyfill such as @formatjs/intl-segmenter where that API is missing. React 18 and 19 are supported, with minimal compatibility back to 16.9. Webpack 4 users will need manual path mapping because it ignores the exports field.

One discrepancy. The README says preset mode covers “Sheets, Docs, and Node profiles”, but the current headless guide (docs v1.3.0) tells you to use plugin mode in Node. npm explains why: @univerjs/preset-sheets-node-core stopped at 1.0.4 on 10 October and pins its dependencies to 1.0.4, while the rest of the SDK is on 1.3.1. Follow the guide.

Caveats, honestly

There’s no .xlsx import or export in the open-source packages. File import/export, printing, charts, pivot tables, real-time collaboration and edit history are all listed under Univer Pro. The README says so, well below the fold, and most people evaluating an Excel-like SDK treat file I/O as table stakes.

The tagline promises more than this repo contains. Those six document types in the GitHub description are Spreadsheets, Docs, Slides, Canvas, Relational Tables and PDF. In the open-source table, Bases are only an extension point, PDF doesn’t appear, and the fuller Slides product is Pro.

Formula edits hit a cliff at 5,000 formulas. Issue #7936 shows the AST cache is a fixed, non-configurable 5,000-entry LRU, and because recalculation walks every formula in order, the hit rate collapses once you exceed it. The reporter measured warm edit latency at 59.5 ms with 4,900 formulas and 108.1 ms with 5,100. It’s open and unassigned.

Only the latest release gets security fixes. That’s what SECURITY.md says, and the file still justifies it by calling Univer pre-1.0, five weeks after 1.0.0 shipped. Combined with the rule that every @univerjs/* package must share one version, upgrades are all-or-nothing. Pre-1.0 they were frequently breaking: v0.25.0 renamed every locale key to a package-prefixed namespace. There’s now an API stability policy. I’d still pin exact versions.

The AI tooling’s licence is unclear. Issue #7747 asks for the terms covering the @univer-cli/* agent packages and notes the published tarballs carry no licence field or LICENSE file. Apache-2.0 covers this repo; don’t assume it covers everything the README links to.

Skip it if you only need a read-only table, where a plain data grid is far lighter, or if your requirement is “users upload Excel files and we keep their formatting”. The open-source build won’t do the second one, so price Pro before you write any code.

Where it sits next to SheetJS

Univer (open source)SheetJS Community Edition
Best atAn editable, formula-aware spreadsheet UI in your app, and the same engine headless in NodeReading and writing .xlsx and .csv in the browser or on a server
Falls down onAny file import or export without a Pro licenceNo editing UI, and it doesn’t calculate formulas

They’re complements, not rivals. For free file I/O, pair them and write the mapping yourself.

What I’d steal

Whether or not Univer lands in my projects, the plugin rule is going into my own code: if a feature might ever need to run without a screen, its mutations don’t get to look at the screen.