Collaborative Editing in EJ2 JavaScript Block Editor control
15 Jul 202613 minutes to read
The Block Editor supports real-time collaborative editing, enabling multiple users to work on the same document simultaneously. Collaboration is powered by Yjs, a Conflict-free Replicated Data Type (CRDT) framework that synchronizes document changes across all connected users and automatically resolves conflicts.
With collaboration enabled, users can:
- Edit the same document in real time.
- View remote user cursors and selections.
- Track active collaborators.
- Perform collaboration-aware undo and redo operations.
- Create, restore, compare, export, and import document versions.
Try the live demo here
Prerequisites
Before enabling collaboration, install the yjs library and a Yjs provider. See Yjs Providers to choose the right provider for your use case.
Inject the Collaboration module into the Block Editor before use.
ej.blockeditor.BlockEditor.Inject(Collaboration);Yjs Providers
A Yjs provider handles the transport of document updates between connected users. Choose a provider based on your deployment requirements.
| Provider | Type | Use Case |
|---|---|---|
y-websocket |
Self-hosted | Production deployments with your own WebSocket server. |
y-webrtc |
Peer-to-peer | Quick local testing and development; no server required. |
y-indexeddb |
Local storage | Offline persistence within a single browser. |
| Hocuspocus | Open-source server | Scalable Node.js server with pluggable storage and Redis support. |
| Liveblocks | Fully managed | Hosted WebSocket infrastructure with REST API and DevTools. |
| PartyKit | Serverless | Serverless provider on Cloudflare; ideal for prototyping. |
Note: For development and testing,
y-webrtcor PartyKit allow you to get started without a server. For production, usey-websocketor a managed provider such as Liveblocks or Hocuspocus for reliable, persistent synchronization.
Configure collaboration settings
Use the collaborationSettings property of type CollaborationSettingsModel to configure collaboration settings for your Block Editor. It provides properties such as provider, enableAwareness, adapter and versionHistory which allows to customize the collaboration behavior.
Getting Started
The following steps will help you set up real-time collaboration in the Block Editor using Yjs.
Step 1: Create a Yjs document
Create a shared Yjs document and XML fragment.
var yDoc = new Y.Doc();
var yFragment = yDoc.getXmlFragment('blockeditor');Step 2: Create a Yjs adapter
Create an adapter that provides the Yjs runtime and the shared fragment to the Block Editor.
var adapter = {
yRuntime: Y,
yXmlFragment: yFragment
}Step 3: Configure a provider
Create a provider that connects users to the same shared document. The following example uses y-websocket for production use. For local development, replace it with y-webrtc or a PartyKit provider — no server setup is required.
Production (y-websocket):
var WebsocketProvider = window.WebsocketProvider;
var provider = new WebsocketProvider(
'wss://your-server-url',
'document-room-id',
yDoc
);Development (y-webrtc):
var WebrtcProvider = window.WebrtcProvider;
var provider = new WebrtcProvider('document-room-id', yDoc);Step 4: Enable Collaboration
Pass the adapter and provider to the Block Editor through the collaborationSettings property.
var blockEditor = new ej.blockeditor.BlockEditor({
collaborationSettings: {
adapter: adapter,
provider: provider
}
});User presence and remote cursors
The Block Editor can display remote cursors, text selection overlays, and user details on hover. To enable these user presence features, set enableAwareness to true in collaborationSettings property.
var blockEditor = new ej.blockeditor.BlockEditor({
collaborationSettings: {
adapter: adapter,
provider: provider,
enableAwareness: true
}
});Configure the current user
Set the current user’s display name and cursor highlight color using the users and currentUserId properties. The avatarBgColor value is used for that user’s remote cursor and text selection overlay. The users property includes id, user and avatarBgColor.
var blockEditor = new ej.blockeditor.BlockEditor({
users: [{
id: 'user-1',
user: 'John Doe',
avatarBgColor: '#e74c3c'
}],
currentUserId: 'user-1'
});Get active users
Retrieve all currently connected users using the users property in the block editor.
var users = ej.blockeditor.users;Version history
Version History allows you to capture document snapshots and restore earlier versions. This is a built-in capability of the Block Editor and does not require a third-party service.
Enable version history
Inject the VersionHistory module and configure the versionHistory property under collaborationSettings property.
ej.blockeditor.BlockEditor.Inject(VersionHistory);
var myStorage = new CustomVersionStorage(`blockeditor-${uniqueId}`);
var blockEditor = new ej.blockeditor.BlockEditor({
collaborationSettings: {
adapter: adapter,
provider: provider,
versionHistory: {
storage: myStorage,
snapshotInterval: 3000
}
}
});Configure snapshot storage
Version snapshots need to be persisted to enable version history across browser sessions. Implement the IVersionStorage interface to provide a custom storage backend for managing snapshots. You can use IndexedDB, a backend database, or any other storage solution suitable for your deployment.
The IVersionStorage interface defines the following methods:
| Method | Signature | Description |
|---|---|---|
saveSnapshot |
(snapshot: VersionSnapshot): Promise<void> |
Persist a snapshot. |
loadAllSnapshots |
(): Promise<VersionSnapshot[]> |
Load all persisted snapshots, ordered by timestamp ascending. |
loadSnapshot |
(id: string): Promise<VersionSnapshot \| null> |
Load a single snapshot by id. |
deleteSnapshot |
(id: string): Promise<void> |
Permanently remove a snapshot by id. |
clearAll |
(): Promise<void> |
Remove all snapshots from storage. |
Access the version history instance
After the Block Editor initializes, retrieve the version history instance and wait for snapshot data to load before calling any version history methods.
var versionHistory = ej.blockeditor.getVersionHistory();
await versionHistory.whenReady();Methods
The following are the methods available in the IVersionHistory:
Create a snapshot
Creates a new snapshot of the current document state with an optional label and metadata.
var snapshot = await versionHistory.createSnapshot({
label: 'Before major update',
modifiedBy: currentUserId
});List snapshots
Retrieves all saved snapshots or a paginated subset. Snapshots are returned in chronological order.
// Retrieve all snapshots
var snapshots = versionHistory.getSnapshots();
// Retrieve a paginated subset — getSnapshots(skip, take)
var snapshots = versionHistory.getSnapshots(20, 40);Rename a snapshot
Updates the label or metadata of an existing snapshot without modifying its content.
await versionHistory.renameSnapshot(snapshotId, 'Release Candidate');Restore a snapshot
Reverts the document to a previously saved snapshot state. The current document state is automatically backed up before restoration.
await versionHistory.restoreSnapshot(snapshotId);Note: When a snapshot is restored, the current document state is automatically
backed up before the restore operation is applied.
Compare versions
Compares two snapshots to identify differences such as added, removed, or modified content.
var diff = versionHistory.compareVersions(snapshotIdA, snapshotIdB);The returned VersionDiff object provides a summary of the differences between the two selected versions.
Export a snapshot
Serializes a snapshot into a portable format that can be stored externally or transferred between systems.
var exported = await versionHistory.exportSnapshot(snapshotId);Exported snapshots can be stored externally or transferred between systems.
Import a snapshot
Imports a previously exported snapshot back into the version history storage.
var imported = await versionHistory.importSnapshot(exported);Events
Use the following event callbacks in versionHistory settings to respond to snapshot life cycle events.
snapshotCreated
Triggered when a new snapshot is created.
var blockEditor = new ej.blockeditor.BlockEditor({
collaborationSettings: {
versionHistory: {
storage: myStorage,
snapshotCreated: function (args) {
console.log(args.snapshot.id);
}
}
}
});snapshotRestored
Triggered when a snapshot is restored.
var blockEditor = new ej.blockeditor.BlockEditor({
collaborationSettings: {
versionHistory: {
storage: myStorage,
snapshotRestored: function (args) {
console.log(args.snapshot.label);
}
}
}
});Best Practices
- Use WebRTC or PartyKit for development - These providers require no server setup and are ideal for local testing and prototyping before moving to a production provider.
-
Use WebSocket-based providers in production -
y-websocket, Hocuspocus, or a managed service like Liveblocks provides reliable, low-latency, persistent synchronization at scale. - Use stable room identifiers - Use a unique document ID as the collaboration room name to prevent unintended document sharing between different documents.
- Persist snapshots externally - Store snapshots in a database or cloud storage to preserve version history across sessions.
-
Enable awareness selectively - Disable
enableAwarenesswhen user presence information is not required to reduce network and processing overhead.
Troubleshooting
Changes Are Not Synchronizing
Verify the following:
- All users are connected to the same collaboration room.
- The provider connection is active.
- The shared Yjs document is correctly configured.
Remote Cursors Are Not Visible
Verify the following:
-
enableAwarenessis set totrue. - The configured provider supports the Yjs awareness protocol.
- User information is set via the
usersandcurrentUserIdproperties. - Each user has a unique
idvalue.
Remote User Names Are Not Appearing on Cursors
Verify the following:
- The
userfield is populated for all entries in theusersarray.
Version History Is Not Available
Verify the following:
- The
VersionHistorymodule is injected into the Block Editor. - A valid
IVersionStorageimplementation is provided. -
whenReady()has been awaited before accessing snapshots.