Add Word-style commenting to documents with threaded discussions, replies, and resolution workflows.
Quick Start
const superdoc = new SuperDoc({
selector: '#editor',
document: 'contract.docx',
user: {
name: 'John Smith',
email: 'john@company.com'
},
modules: {
comments: {
readOnly: false,
allowResolve: true,
element: '#comments'
}
},
onCommentsUpdate: ({ type, comment }) => {
console.log('Comment event:', type);
}
});
Module Configuration
View-only mode, prevents new comments
Allow marking comments as resolved
Container for comments sidebar
Enable dual internal/external comment system
Hide internal comments from view
During initialization
modules: {
comments: {
element: '#comments-sidebar'
}
}
After initialization
superdoc.on('ready', () => {
const sidebar = document.querySelector('#comments-sidebar');
superdoc.addCommentsList(sidebar);
});
Fired for all comment changes.
Event type
pending
- User selected text, about to add comment
add
- New comment created
update
- Comment text edited
delete
- Comment removed
resolved
- Comment marked resolved
selected
- Comment clicked/selected
onCommentsUpdate: ({ type, comment, meta }) => {
switch(type) {
case 'pending':
showCommentDialog();
break;
case 'add':
await saveComment(comment);
break;
case 'resolved':
await markResolved(comment.id);
break;
}
}
Comment object
Position in documentVisual bounds (top, left, bottom, right)
‘insertion’ or ‘deletion’
Threaded Replies
Comments support nested discussions:
// Parent comment
const parent = {
commentId: 'parent-123',
commentText: 'Should we change this?',
parentCommentId: null
};
// Reply
const reply = {
commentId: 'reply-456',
commentText: 'Yes, let\'s update',
parentCommentId: 'parent-123'
};
// Nested reply
const nested = {
commentId: 'reply-789',
commentText: 'Done',
parentCommentId: 'reply-456'
};
Resolution Workflow
onCommentsUpdate: ({ type, comment }) => {
if (type === 'resolved') {
const resolution = {
commentId: comment.commentId,
resolvedTime: comment.resolvedTime,
resolvedByName: comment.resolvedByName,
resolvedByEmail: comment.resolvedByEmail
};
await api.resolveComment(resolution);
notifyParticipants(comment);
}
}
Permission Control
const canResolve = (comment, user, role) => {
// Document owners can always resolve
if (role === 'editor') return true;
// Authors can resolve their own
if (comment.creatorEmail === user.email) return true;
return false;
};
Word Import/Export
Word comments are automatically imported with the document and marked with importedId
const importedComment = {
commentId: 'uuid-generated',
importedId: 'w15:123', // Original Word ID
importedAuthor: {
name: 'John Smith (imported)',
email: 'john@company.com'
},
parentCommentId: 'parent-uuid' // Relationships preserved
};
Export mode
external
- Include comments in DOCX
clean
- Remove all comments
- Custom function for filtering
// With comments
const blob = await superdoc.export({
commentsType: 'external',
isFinalDoc: false
});
// Clean version
const cleanBlob = await superdoc.export({
commentsType: 'clean',
isFinalDoc: true
});
Track Changes Integration
Comments automatically generate for tracked changes:
Special comment for track changes
‘insertion’ or ‘deletion’
API Methods
Add comment to selected text.
superdoc.activeEditor.commands.insertComment({
commentText: 'Review this section',
creatorName: 'John Smith',
creatorEmail: 'john@company.com'
});
superdoc.activeEditor.commands.removeComment({
commentId: 'comment-123'
});
superdoc.activeEditor.commands.setActiveComment({
commentId: 'comment-123'
});
superdoc.activeEditor.commands.resolveComment({
commentId: 'comment-123'
});
Common Patterns
Contract Review Workflow
modules: {
comments: {
allowResolve: false, // Only owner resolves
element: '#legal-comments'
}
}
onCommentsUpdate: ({ type, comment }) => {
// Track progress
if (type === 'add') stats.total++;
if (type === 'resolved') stats.resolved++;
// Auto-assign
if (comment.commentText.match(/\bprice|cost\b/i)) {
assignToFinanceTeam(comment);
}
}
Internal Review System
modules: {
comments: {
useInternalExternalComments: true
}
}
onCommentsUpdate: ({ comment }) => {
if (comment.isInternal) {
syncToInternalTeam(comment);
} else {
syncToAllUsers(comment);
}
}
import { debounce } from 'lodash';
const saveComment = debounce(async (comment) => {
await api.updateComment(comment.commentId, comment);
}, 1000);
onCommentsUpdate: ({ type, comment }) => {
if (type === 'update') {
saveComment(comment);
}
}
For documents with many comments:
- Implement viewport-based loading
- Virtualize comment lists
- Set reasonable limits (e.g., 500 comments max)
- Limit comment length (e.g., 10,000 characters)
const MAX_COMMENTS = 500;
onCommentsUpdate: ({ type }) => {
if (type === 'add' && commentCount >= MAX_COMMENTS) {
showWarning('Maximum comments reached');
return false;
}
}