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

modules.comments.readOnly
boolean
default:"false"
View-only mode, prevents new comments
modules.comments.allowResolve
boolean
default:"true"
Allow marking comments as resolved
modules.comments.element
string | HTMLElement
Container for comments sidebar
modules.comments.useInternalExternalComments
boolean
default:"false"
Enable dual internal/external comment system
modules.comments.suppressInternalExternalComments
boolean
default:"false"
Hide internal comments from view

Setting Up Comments UI

During initialization
modules: {
  comments: {
  element: '#comments-sidebar'
  }
}
After initialization
superdoc.on('ready', () => {
  const sidebar = document.querySelector('#comments-sidebar');
  superdoc.addCommentsList(sidebar);
});

Comment Events

onCommentsUpdate

Fired for all comment changes.
type
string
required
Event type
comment
Comment
required
Comment object
meta
Object
Additional metadata
onCommentsUpdate: ({ type, comment, meta }) => {
  switch(type) {
    case 'pending':
      showCommentDialog();
      break;
    case 'add':
      await saveComment(comment);
      break;
    case 'resolved':
      await markResolved(comment.id);
      break;
  }
}

Comment Data Structure

comment
Object
Comment object

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

Marking Comments Resolved

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

Importing Comments

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
};

Exporting Comments

commentsType
string
required
Export mode
// 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:
trackChangeComment
Object
Special comment for track changes

API Methods

Adding Comments

insertComment

Add comment to selected text.
options
Object
required
Comment options
superdoc.activeEditor.commands.insertComment({
  commentText: 'Review this section',
  creatorName: 'John Smith',
  creatorEmail: 'john@company.com'
});

Managing Comments

removeComment

options.commentId
string
required
Comment to remove
superdoc.activeEditor.commands.removeComment({
  commentId: 'comment-123'
});

setActiveComment

options.commentId
string
required
Comment to highlight
superdoc.activeEditor.commands.setActiveComment({
  commentId: 'comment-123'
});

resolveComment

options.commentId
string
required
Comment to resolve
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);
  }
}

Auto-save Comments

import { debounce } from 'lodash';

const saveComment = debounce(async (comment) => {
  await api.updateComment(comment.commentId, comment);
}, 1000);

onCommentsUpdate: ({ type, comment }) => {
  if (type === 'update') {
    saveComment(comment);
  }
}

Performance Considerations

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;
  }
}