Collaboration integration

Connect the Univer editor to a collaboration service so users can edit the same document.

Collaboration connects browser editors to a shared document service. For example, two users open the same spreadsheet, and an edit in one browser appears in the other.

Prepare the backend using server integration, then configure the editor and document loading below. Follow the paired Server and Web code in Collaboration Quick Start.

100%

Connect your existing editor

Keep your Web SDK editor. Follow the browser configuration in Quick Start to add collaboration plugins and configure document loading, edit submission, and WebSocket addresses. Keep browser and server package versions compatible.

Fixed example users and default permissions are for demonstration. In your application, include login credentials in requests so the backend can verify identity and document access. See license configuration for licensing.

Create and open the same document

Creating and opening are separate actions:

  1. When a user creates a document, your backend saves it and returns its ID.
  2. The browser loads that ID. Reuse it when reopening the document or sharing it with another user.

The API's unitId is this document ID. Do not create a new local blank document on every visit.

Set the current user before loading a document. The current user identifies the collaborator and owns the local offline cache; see Identity and authorization.

For example, after registering Sheets and collaboration plugins, load a server spreadsheet:

TypeScript
import { UserManagerService } from '@univerjs/core'import '@univerjs-pro/collaboration-client/facade'univer.__getInjector().get(UserManagerService).setCurrentUser({ userID, name, avatar })const workbook = await univerAPI.getCollaboration().loadSheetAsync('your-document-id')if (!workbook) throw new Error('Document could not be loaded')

See the Sheets, Docs, Slides, Boards, Bases, and PDFs guides for product-specific loading. Check support in the product and target backend release.

Verify synchronization, saving, and permissions

Open the same document in two independent browser sessions, such as a regular and private window. Edit in one and check the other. Then verify content after refresh, reconnection, and server restart.

Test with an editor, viewer, and unauthorized user. Read-only UI settings control page interactions; the server must still reject unauthorized reads and changes.

If documents load but edits do not synchronize, check the WebSocket connection. If content disappears after restart, check backend storage. See server integration for those settings.

Add exchange, comments, or history

To edit an imported file together, create a collaborative document from its converted data; see file exchange integration. Comments and history need their own plugins, services, and permissions. Follow Office extension modules.

For history panel configuration, see Sheets, Docs, Slides, Boards, Bases. See PDFs history for its current scope.

Synchronization status and retries

Listen after importing the collaboration Facade. CollaborationStatusChanged reports the current status of units already collaborating when the listener is registered, then reports changes without consecutive duplicate statuses. hasWritePending(unitId) includes queued, awaiting-acknowledgement, and offline edits. Without an ID it checks the focused unit. A connected socket alone does not mean edits are saved.

TypeScript
import '@univerjs-pro/collaboration-client/facade'const collaboration = univerAPI.getCollaboration()const statusSubscription = univerAPI.addEvent(univerAPI.Event.CollaborationStatusChanged, (event) => {  console.info(event.unitId, event.status, collaboration.hasWritePending(event.unitId))})const resendSubscription = univerAPI.addEvent(univerAPI.Event.CollaborationChangesetResend, (event) => {  console.info(event.unitId, event.reason, event.attempt, event.unsyncedMutationCount, event.nextDelayMs)})

CollaborationChangesetResend fires on resends, not the first submission. Reasons are ack_timeout, request_failed, license_editing_connections_exceeded, should_retry, and reconnect; attempt, elapsedMs, unsyncedMutationCount, and nextDelayMs support a saving/retrying indicator. Dispose event subscriptions when removing the editor.

request_failed means a retryable request failure, such as a network error or HTTP 408, 429, or 5xx. license_editing_connections_exceeded means the licensed editing connections are all in use and the server requested a resend.

Batching delay and acknowledgement timeout are different. The client plugin defaults to sendChangesetTimeout: 2000, changesetAckTimeout: 20000, and maxChangesetAckTimeout: 120000 (milliseconds). The submission request uses the acknowledgement timeout too; unacknowledged changesets are resent with exponential backoff up to the maximum. Equal acknowledgement and maximum timeouts give a constant interval.

TypeScript
import { UniverCollaborationClientPlugin } from '@univerjs-pro/collaboration-client'import { BrowserCollaborationSocketService } from '@univerjs-pro/collaboration-client-ui'univer.registerPlugin(UniverCollaborationClientPlugin, {  socketService: BrowserCollaborationSocketService,  sendChangesetTimeout: 200,  changesetAckTimeout: 20_000,  maxChangesetAckTimeout: 120_000,})

Merge these options into your existing client registration, keeping its socket and endpoint configuration. The Sheets and Docs collaboration presets use a 200 ms batching delay and do not expose the acknowledgement timeout options; use plugin mode when customizing these timeouts.

Handle conflicts in the host

CollaborationConflict fires once when a unit enters conflict, after the offline-cache cleanup attempt. Register the listener before loading documents. Its reason is transform_failed, apply_failed, changeset_rejected, license_unit_type_rejected, permission_rejected, or remote_revert; message and revert provide additional details when available.

license_unit_type_rejected means the server license does not cover collaboration on this document type.

TypeScript
import type { IEventParamConfig } from '@univerjs/core/facade'import '@univerjs-pro/collaboration-client/facade'const conflicts = new Map<string, IEventParamConfig['CollaborationConflict']>()const conflictSubscription = univerAPI.addEvent(univerAPI.Event.CollaborationConflict, (event) => {  conflicts.set(event.unitId, event)})

The example retains the event for a host recovery screen. This Map is only in memory: before refreshing, save or export any localChanges through your application. The event is the opportunity to retain them; later reads cannot recover cleared cache data. awaitingChangeset may already have been applied on the server, so do not blindly replay it.

cacheCleared: false means refreshing would replay the old cached edits. Resolve cache cleanup before refreshing. Clearing the cache does not mean unsynchronized edits were saved. A permission_rejected conflict also requires resolving access; reloading does not grant permission.

By default the UI shows a reason-specific conflict notice and offers reload after successful cache cleanup, or retry if cleanup failed. To own the recovery flow, set enableDefaultConflictUI: false in the existing initialization and handle the event yourself:

TypeScript
import { UniverCollaborationClientUIPlugin } from '@univerjs-pro/collaboration-client-ui'univer.registerPlugin(UniverCollaborationClientUIPlugin, { enableDefaultConflictUI: false })

The collaboration presets belong in the existing createUniver({ presets: [...] }) configuration alongside the editor preset. Keep your endpoint and history settings. See event payloads and client configuration.

Server reasons and license notices

For server rejections, CollaborationConflict.message contains the server’s reason when provided; do not parse localized message text to decide the recovery action. Branch on reason. license_unit_type_rejected requires a server license that covers the document type, while license_editing_connections_exceeded is a resend signal: edits remain unacknowledged and continue retrying. Keep the editor open until hasWritePending(unitId) becomes false or the application has retained the edits.

The default UI displays an editing-capacity notice and, on a resend at least 20 seconds after the first send, a save-timeout notice. These are removed when the unit leaves the awaiting states or is disposed. A domain rejection also has a dedicated notice. enableDefaultConflictUI: false disables conflict recovery notices only; it does not disable these waiting or domain notices.

How is this guide?

© 2026 DreamNum Co., Ltd.