Headless Univer

Headless Univer is Univer’s content runtime without an interactive editor UI. It shares content models, commands, calculation, and Facade APIs with the browser editor. It can process content in Node.js or run UI-free tasks in a browser. Available capabilities depend on the registered product and feature plugins.

In Node.js, you can create or load workbooks, read and write cells and formatting, change rows and columns, calculate formulas, and save Univer snapshots. Filtering, sorting, validation, pivots, and charts require their content plugins. Changing chart data does not render a chart.

The example below uses plugin mode. @univerjs/core and product content plugins belong to the Headless content layer; @univerjs/ui, product *-ui packages, and default browser core presets do not belong in this configuration. The example retains @univerjs/engine-render as a shared dependency without mounting a Canvas or editor. See Headless package boundaries for the classification and exceptions.

Local snapshot operations need no collaboration service. The collaboration example additionally needs a client, server, and authentication; Office conversion and screenshots require Server SDK and a rendering runtime, respectively. Importing /facade alone neither registers feature plugins nor makes UI APIs Headless APIs.

Installation

Shell
pnpm add @univerjs/core @univerjs/engine-render @univerjs/engine-formula @univerjs/docs @univerjs/sheets @univerjs/sheets-formula @univerjs/sheets-numfmt

Usage

TypeScript
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'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,    ),  },})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({})

Collaborative editing in Node.js

Node.js can act as a collaboration client: load an existing workbook from the server, modify it through the Facade API, and synchronize changes to other clients with the same document open. Before running the example, prepare a collaboration service, a client license, and an accessible document ID.

Example files

Organize the files as follows:

text
src/sheets/node/├── main.ts├── univer.ts└── worker.ts
  • univer.ts: Creates Univer and registers the feature plugins, collaboration client, and optional RPC main-process plugin.
  • worker.ts: Registers the calculation-side formula engine, remote formula, filter, and pivot table plugins, and communicates with the main process over RPC.
  • main.ts: Loads the collaborative document and demonstrates permissions, basic sheet operations, conditional formatting, data validation, tables, pivot tables, sparklines, images, charts, shapes, grouping, and hyperlinks.

Replace your-license in both files with the same license, and replace the server URL, Cookie, document ID, and user IDs. The permission example in main.ts also configures collaborators and protected ranges; use actual application users and an identity with the required management permissions.

TypeScript
import { UniverCollaborationPlugin } from '@univerjs-pro/collaboration'import { UniverCollaborationClientPlugin } from '@univerjs-pro/collaboration-client'import {  NodeCollaborationSocketService,  UniverCollaborationClientNodePlugin,} from '@univerjs-pro/collaboration-client-node'import { UniverProFormulaEnginePlugin } from '@univerjs-pro/engine-formula'import { UniverLicensePlugin } from '@univerjs-pro/license'import { UniverSheetsChartPlugin } from '@univerjs-pro/sheets-chart'import { UniverSheetsOutlinePlugin } from '@univerjs-pro/sheets-outline'import { UniverSheetsPivotTablePlugin } from '@univerjs-pro/sheets-pivot'import { UniverSheetsShapePlugin } from '@univerjs-pro/sheets-shape'import { UniverSheetSparklinePlugin } from '@univerjs-pro/sheets-sparkline'import { IAuthzIoService, IImageIoService, IUndoRedoService, LocaleType, Univer } from '@univerjs/core'import { UniverDataValidationPlugin } from '@univerjs/data-validation'import { UniverDocsPlugin } from '@univerjs/docs'import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing'import { UniverDrawingPlugin } from '@univerjs/drawing'import { UniverRenderEnginePlugin } from '@univerjs/engine-render'import { UniverNetworkPlugin } from '@univerjs/network'import { UniverRPCNodeMainPlugin } from '@univerjs/rpc-node'import { UniverSheetsPlugin } from '@univerjs/sheets'import { UniverSheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting'import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'import { UniverSheetsDrawingPlugin } from '@univerjs/sheets-drawing'import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link'import { UniverSheetsNotePlugin } from '@univerjs/sheets-note'import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort'import { UniverSheetsTablePlugin } from '@univerjs/sheets-table'import { UniverThreadCommentPlugin } from '@univerjs/thread-comment'import path from 'node:path'// The Facade APIs used here are part of packages/facade/src/apis/everything.ts// Some plugin implementations are incomplete, so their APIs are not included here.import '@univerjs/engine-formula/facade'import '@univerjs/sheets/facade'import '@univerjs/sheets-formula/facade'import '@univerjs/sheets-numfmt/facade'import '@univerjs/sheets-conditional-formatting/facade'import '@univerjs/sheets-data-validation/facade'import '@univerjs/sheets-filter/facade'import '@univerjs/sheets-hyper-link/facade'import '@univerjs/sheets-note/facade'import '@univerjs/sheets-table/facade'import '@univerjs/sheets-drawing/facade'import '@univerjs/sheets-thread-comment/facade'import '@univerjs-pro/engine-shape/facade'import '@univerjs-pro/sheets-pivot/facade'import '@univerjs-pro/sheets-sparkline/facade'import '@univerjs-pro/sheets-chart/facade'import '@univerjs-pro/sheets-shape/facade'import '@univerjs-pro/sheets-outline/facade'import '@univerjs-pro/collaboration-client/facade'export interface ICreateUniverOnNodeOptions {  useComputingWorker?: boolean}export function createUniverOnNode(options: ICreateUniverOnNodeOptions = {}): Univer {  const { useComputingWorker = false } = options  const univer = new Univer({    locale: LocaleType.EN_US,    locales: {},    override: [      [IAuthzIoService, null],      [IUndoRedoService, null],    ],  })  univer.registerPlugin(UniverLicensePlugin, {    license: 'your-license',  })  registerBasicPlugins(univer, useComputingWorker)  registerSharedPlugins(univer)  if (useComputingWorker) {    registerRPCPlugin(univer)  }  registerDocPlugins(univer)  registerSheetPlugins(univer, useComputingWorker)  registerCollaborationPlugins(univer)  return univer}function registerBasicPlugins(univer: Univer, useComputingWorker: boolean): void {  univer.registerPlugin(UniverProFormulaEnginePlugin, { notExecuteFormula: useComputingWorker })}function registerSharedPlugins(univer: Univer): void {  univer.registerPlugin(UniverRenderEnginePlugin)  univer.registerPlugin(UniverThreadCommentPlugin)  univer.registerPlugin(UniverDrawingPlugin, {    override: [[IImageIoService, null]],  })}function registerDocPlugins(univer: Univer): void {  univer.registerPlugin(UniverDocsPlugin)  univer.registerPlugin(UniverDocsDrawingPlugin)}function registerSheetPlugins(univer: Univer, useComputingWorker: boolean): void {  univer.registerPlugin(UniverSheetsPlugin, { notExecuteFormula: useComputingWorker })  univer.registerPlugin(UniverSheetsFormulaPlugin, { notExecuteFormula: useComputingWorker })  univer.registerPlugin(UniverSheetsNumfmtPlugin)  univer.registerPlugin(UniverSheetsConditionalFormattingPlugin)  univer.registerPlugin(UniverDataValidationPlugin)  univer.registerPlugin(UniverSheetsDataValidationPlugin)  univer.registerPlugin(UniverSheetsFilterPlugin)  univer.registerPlugin(UniverSheetsHyperLinkPlugin)  univer.registerPlugin(UniverSheetsNotePlugin)  univer.registerPlugin(UniverSheetsDrawingPlugin)  univer.registerPlugin(UniverSheetsSortPlugin)  univer.registerPlugin(UniverSheetsTablePlugin)  univer.registerPlugin(UniverSheetsChartPlugin)  univer.registerPlugin(UniverSheetsShapePlugin)  univer.registerPlugin(UniverSheetsPivotTablePlugin, {    notExecuteFormula: useComputingWorker ? true : undefined,  })  univer.registerPlugin(UniverSheetSparklinePlugin)  univer.registerPlugin(UniverSheetsOutlinePlugin)}function registerRPCPlugin(univer: Univer): void {  // Set the worker path to the actual location of the build output.  const childPath = path.join(__dirname, 'worker.js')  univer.registerPlugin(UniverRPCNodeMainPlugin, { workerSrc: childPath })}function registerCollaborationPlugins(univer: Univer): void {  const serverUrl = 'https://your-univer-server.example.com'  univer.registerPlugin(UniverNetworkPlugin)  univer.registerPlugin(UniverCollaborationPlugin)  univer.registerPlugin(UniverCollaborationClientPlugin, {    socketService: NodeCollaborationSocketService,    enableOfflineEditing: false,    enableSingleActiveInstanceLock: false,    snapshotServerUrl: `${serverUrl}/universer-api/snapshot`,    collabSubmitChangesetUrl: `${serverUrl}/universer-api/comb`,    collabWebSocketUrl: `${serverUrl}/universer-api/comb/connect`,    wsSessionTicketUrl: `${serverUrl}/universer-api/user/session-ticket`,    uploadFileServerUrl: `${serverUrl}/universer-api/stream/file/upload`,    signUrlServerUrl: `${serverUrl}/universer-api/file/{fileID}/sign-url`,    downloadEndpointUrl: `${serverUrl}`,    authzUrl: `${serverUrl}/universer-api/authz`,    sendChangesetTimeout: 200,    retryConnectingInterval: 1000,    customHeaders: {      // Example: If your Univer server requires authentication via cookies, you can set the 'cookie' header here.      cookie: 'your-cookie',      // ... more headers    },  })  univer.registerPlugin(UniverCollaborationClientNodePlugin)}

Enable the calculation worker

createUniverOnNode() defaults to useComputingWorker: false, so formulas run in the current process. The main.ts example above explicitly passes useComputingWorker: true to enable the calculation worker:

  1. In the main process, UniverProFormulaEnginePlugin, UniverSheetsPlugin, UniverSheetsFormulaPlugin, and UniverSheetsPivotTablePlugin all set notExecuteFormula: true to delegate calculation to the child process through RPC.
  2. UniverRPCNodeMainPlugin starts the built worker.js specified by workerSrc.
  3. The child process receives data and performs calculations through UniverRPCNodeWorkerPlugin and UniverRemoteSheetsFormulaPlugin.

UniverSheetsPivotTablePlugin distinguishes three modes: set true in the main process when using a worker, set false in the worker, and omit the option or set undefined when running without a worker so that both pivot table services and calculation logic are registered in the current process. Therefore, the main process uses useComputingWorker ? true : undefined rather than passing useComputingWorker directly.

Node.js starts this worker with child_process.fork(). Use your existing build tool to build main.ts and worker.ts as separate entry points, and set workerSrc to the built worker file. The example assumes worker.js is in the same directory as the main entry output; adjust the path, extension, and module format to match your build. It uses CommonJS __dirname to locate the file; ESM projects should use the corresponding path resolution method.

Runtime notes

loadSheetAsync() loads an existing server document; you do not need to call createWorkbook(). The collaboration service identifies the user and checks permissions through authentication information such as the Cookie in customHeaders. The server returns the connection’s memberID.

This example disables browser offline caching and the single-active-instance lock across tabs, so there is no need to call setCurrentUser() for cache isolation between accounts. If you later use features such as comments that depend on the local current user, you can set the application user through UserManagerService; this does not replace server authentication.

The example keeps the collaboration connection open after running. Open the same document in a browser to see the changes. flush() waits for server acknowledgement of queued collaboration changes. For a one-shot script, call univer.dispose() after all asynchronous operations and synchronization have finished.

How is this guide?

© 2026 DreamNum Co., Ltd.