Configure SuperDoc on the client-side to enable real-time collaborative editing with multiple users.
Quick Start
import { SuperDoc } from 'superdoc' ;
const superdoc = new SuperDoc ({
selector: '#editor' ,
document: {
id: 'doc-123' ,
type: 'docx' ,
isNewFile: false ,
},
user: {
name: 'John Smith' ,
email: '[email protected] '
},
modules: {
collaboration: {
url: 'ws://localhost:3050/doc' ,
token: 'auth-token'
}
}
});
Configuration Options
Basic Setup
Collaboration module configuration
modules.collaboration.url
WebSocket URL for the collaboration server // For production
url : 'wss://collab.example.com/doc'
// For development
url : 'ws://localhost:3050/doc'
// With dynamic document routing
url : ` ${ wsUrl } /doc`
modules.collaboration.token
Authentication token for server validation token : 'jwt-token-here'
// or
token : await getAuthToken ()
modules.collaboration.params
Additional connection parameters sent to the server. These will be exposed in server-side collaborationParams . params : {
apiKey : 'your-api-key' ,
projectId : 'project-123' ,
customParam : 'value'
}
modules.collaboration.providerType
string
default: "'superdoc'"
WebSocket provider implementation to use
'superdoc' - Default WebSocket provider using y-websocket
'hocuspocus' - Hocuspocus provider for advanced features
providerType : 'hocuspocus' // or 'superdoc'
modules.collaboration.preserveConnection
Whether to preserve WebSocket connection (Hocuspocus provider only) preserveConnection : true // Keep connection alive
Document Configuration
Document configuration for collaboration
Unique document identifier - used as collaboration room ID document : {
id : route . params . documentId , // From URL params
type : 'docx' ,
isNewFile : false
}
Document type - currently supports 'docx'
Whether this is a new document or loading existing content
User Configuration
Current user information for collaboration awareness
Display name shown to other users
Unique user identifier (used for user presence)
Avatar URL for user presence display
User Presence Colors
Color palette for user cursors and selections colors : [
'#a11134' , // Red
'#2a7e34' , // Green
'#b29d11' , // Yellow
'#2f4597' , // Blue
'#ab5b22' // Brown
]
Users automatically receive unique colors from this palette.
Event Handlers
onReady Event
Triggered when SuperDoc and collaboration are fully initialized.
Called when SuperDoc is ready and collaboration is connected onReady : ( event ) => {
console . log ( 'SuperDoc is ready' , event );
const editor = event . superdoc . activeEditor ;
// Access the Yjs document for custom functionality
const ydoc = event . superdoc . ydoc ;
if ( ydoc && editor ) {
setupCustomObservers ( ydoc , editor );
}
}
event.superdoc.activeEditor
The active ProseMirror editor instance
The Yjs document for collaboration state
onAwarenessUpdate Event
Handle user presence updates (users joining/leaving).
Called when user awareness state changes const onAwarenessUpdate = ({ states , added , removed , superdoc }) => {
// Handle removed users first
if ( removed && removed . length > 0 ) {
removed . forEach ( clientId => {
const index = connectedUsers . value . findIndex ( user => user . clientId === clientId );
if ( index !== - 1 ) {
console . log ( "Removing user:" , connectedUsers . value [ index ]);
connectedUsers . value . splice ( index , 1 );
}
});
}
// Handle added users
if ( added && added . length > 0 ) {
added . forEach ( clientId => {
const userState = states . find ( user => user . clientId === clientId );
if ( userState && userState . name && ! connectedUsers . value . find ( u => u . clientId === clientId )) {
console . log ( "Adding user:" , userState );
connectedUsers . value . push ( userState );
}
});
}
// Fallback: initial load when no added/removed events
if (( ! added || added . length === 0 ) && ( ! removed || removed . length === 0 )) {
if ( states ) {
connectedUsers . value = states . filter ( user => user && user . name );
console . log ( "Initial user load:" , connectedUsers . value );
}
}
};
Array of all current user awareness states with their information
Array of client IDs for users that joined
Array of client IDs for users that left
Reference to the SuperDoc instance
User State Properties
Each user object in the states array contains:
Unique client identifier for this user session
User’s email address (unique identifier)
Hex color assigned to this user for cursors and selections
Current cursor position information (if available)
Current text selection information (if available)
onEditorCreate Event
Called when each editor instance is created. This can be used do things like load a default document if the current document loaded from a server is blank. onEditorCreate : async ( event ) => {
const { editor } = event ;
if ( ! editor ?. state ) return ;
const textContent = editor . state . doc . textContent ;
// Check if document is empty (no content or only whitespace)
const isEmpty = ! textContent || textContent . trim (). length === 0 ;
if ( ! isEmpty ) return ;
try {
// Fetch and load default.docx
const response = await fetch ( defaultDocument );
const blob = await response . blob ();
const file = new File ([ blob ], 'default.docx' , { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
await editor . replaceFile ( file );
} catch ( error ) {
console . error ( 'Error loading default content:' , error );
}
}
onContentError Event
Called when document content fails to load onContentError : ({ error , documentId , file }) => {
console . error ( 'Content loading error:' , error );
showErrorToUser ( 'Failed to load document' );
}
Handle image and media uploads in collaborative documents.
Custom image upload handler for collaboration const handleImageUpload = ( file ) => {
return new Promise (( resolve , reject ) => {
const reader = new FileReader ();
reader . onload = ( event ) => {
const dataUrl = event . target . result ;
const mediaPath = `word/media/ ${ file . name } ` ;
// Store in Yjs shared state for collaboration
if ( superdoc . value ?. ydoc ) {
const mediaMap = superdoc . value . ydoc . getMap ( 'media' );
mediaMap . set ( mediaPath , dataUrl );
}
resolve ( dataUrl );
};
reader . onerror = reject ;
reader . readAsDataURL ( file );
});
};
Complete Example
Here’s a complete Vue.js example based on the production implementation:
< script setup >
import { SuperDoc } from 'superdoc' ;
import { onMounted , onBeforeUnmount , shallowRef , ref } from 'vue' ;
const superdoc = shallowRef ( null );
const connectedUsers = ref ([]);
const currentUser = ref ( null );
const generateUserInfo = async () => {
const response = await fetch ( '/api/user' );
return await response . json ();
};
const handleImageUpload = ( file ) => {
return new Promise (( resolve , reject ) => {
const reader = new FileReader ();
reader . onload = ( event ) => {
const dataUrl = event . target . result ;
const mediaPath = `word/media/ ${ file . name } ` ;
if ( superdoc . value ?. ydoc ) {
const mediaMap = superdoc . value . ydoc . getMap ( 'media' );
mediaMap . set ( mediaPath , dataUrl );
}
resolve ( dataUrl );
};
reader . onerror = reject ;
reader . readAsDataURL ( file );
});
};
const onAwarenessUpdate = ({ states }) => {
const activeUsers = Array . from ( states . values ())
. filter ( state => state . user && state . user . name );
connectedUsers . value = activeUsers ;
};
const init = async () => {
const documentId = 'my-document-id' ;
const wsUrl = 'ws://localhost:3050' ;
const user = await generateUserInfo ();
currentUser . value = user ;
superdoc . value = new SuperDoc ({
selector: '#superdoc' ,
toolbar: '#superdoc-toolbar' ,
document: {
id: documentId ,
type: 'docx' ,
isNewFile: false ,
},
pagination: true ,
colors: [ '#a11134' , '#2a7e34' , '#b29d11' , '#2f4597' , '#ab5b22' ],
user ,
modules: {
collaboration: {
url: ` ${ wsUrl } /doc` ,
token: 'auth-token' ,
},
},
handleImageUpload ,
onAwarenessUpdate ,
onReady : ( event ) => {
const editor = event . superdoc . activeEditor ;
const ydoc = event . superdoc . ydoc ;
if ( ydoc && editor ) {
setupMediaObserver ( ydoc , editor );
}
},
onContentError : ({ error , documentId }) => {
console . error ( 'Content loading error:' , error , documentId );
}
});
};
onMounted (() => init ());
onBeforeUnmount (() => {
if ( superdoc . value ) {
superdoc . value . destroy ();
}
});
</ script >
< template >
< div class = "collaboration-container" >
< div id = "superdoc-toolbar" ></ div >
< div id = "superdoc" ></ div >
< div class = "user-presence" >
< div v-for = " user in connectedUsers " : key = " user . email " >
{{ user . name }}
</ div >
</ div >
</ div >
</ template >
Authentication Patterns
JWT Token Authentication
const getAuthToken = async () => {
const response = await fetch ( '/api/auth/token' , {
credentials: 'include'
});
const { token } = await response . json ();
return token ;
};
const superdoc = new SuperDoc ({
// ... other config
modules: {
collaboration: {
url: 'wss://server.com/doc' ,
token: await getAuthToken ()
}
}
});
Dynamic Document URLs
// For multi-tenant applications
const documentId = route . params . documentId ;
const orgId = route . params . orgId ;
modules : {
collaboration : {
url : `wss://server.com/orgs/ ${ orgId } /docs/ ${ documentId } ` ,
token : authToken
}
}
Error Handling
// Handle authentication failures
superdoc . provider ?. on ( 'authenticationFailed' , async ({ reason }) => {
if ( reason === 'token-expired' ) {
const newToken = await refreshAuthToken ();
superdoc . provider . configuration . token = newToken ;
superdoc . provider . connect ();
} else {
redirectToLogin ();
}
});
// Handle connection errors
superdoc . provider ?. on ( 'connectionError' , ({ error }) => {
console . error ( 'Connection error:' , error );
showUserNotification ( 'Connection failed. Retrying...' );
});
Limit concurrent users : Keep under 50 active users per document for optimal performance
Efficient awareness updates : Only update UI when user list actually changes
Media handling : Compress images before storing in Yjs maps
Connection monitoring : Implement proper reconnection logic with exponential backoff
Next Steps
Set Up Backend Configure your collaboration server to handle client connections