Skip to main content
Word-style revision tracking. Every edit carries an author, a timestamp, and an id. Accept or reject one at a time, by selection, or in bulk. Changes round-trip through DOCX as native Word revisions — import, edit, export, nothing lost.

Quick start

const superdoc = new SuperDoc({
  selector: "#editor",
  document: "contract.docx",
  documentMode: "suggesting", // record new edits as tracked changes
  user: {
    name: "John Smith",
    email: "john@company.com",
  },
  modules: {
    trackChanges: {
      visible: true,
    },
  },
  onCommentsUpdate: (payload) => {
    if (payload.type === "trackedChange") {
      console.log(payload.event, payload.trackedChangeType, payload.changeId);
    }
  },
});
Tracked-edit recording follows documentMode. Set documentMode: 'suggesting' (or call superdoc.setDocumentMode('suggesting') later) to start recording new edits as revisions.

Configuration

modules.trackChanges.visible
boolean
default:"false"
Show tracked-change markup when documentMode is viewing.
modules.trackChanges.mode
'review' | 'original' | 'final' | 'off'
Rendering mode.
modules.trackChanges.enabled
boolean
default:"true"
Whether the layout engine treats tracked changes as active. Turn off to render the document without any revision UI.
modules.trackChanges.replacements
'paired' | 'independent'
default:"'paired'"
How a tracked replacement (typing over selected text) surfaces in the API and UI. See Revision model.

Viewing mode visibility

Tracked-change markup is hidden by default when documentMode is 'viewing'. Flip modules.trackChanges.visible to show it in read-only mode.
new SuperDoc({
  selector: "#viewer",
  document: "contract.docx",
  documentMode: "viewing",
  modules: {
    trackChanges: { visible: true },
  },
});
The top-level trackChanges key still works as a deprecated alias for modules.trackChanges and prints a one-time console warning.

Revision model

SuperDoc supports two models for how a tracked replacement (an insertion paired with a deletion, created when a user types over selected text) shows up in the API and UI. Pick the one that matches the editor your users expect.
Modelmodules.trackChanges.replacementsBehavior
Paired (default — Google Docs)'paired'Both halves share one id. One accept/reject resolves both. One sidebar row per replacement.
Independent (Microsoft Word / ECMA-376 §17.13.5)'independent'Each insertion and each deletion has its own id. Accept/reject resolves one side at a time. A replacement produces two sidebar rows.
Both modes round-trip cleanly through DOCX — the OOXML always emits one <w:ins> / <w:del> per mark. The difference is how the API surfaces the revisions at runtime.

Paired (default)

const superdoc = new SuperDoc({
  selector: "#editor",
  document: yourFile,
  // default: modules.trackChanges.replacements === 'paired'
});

Independent (Word-style)

const superdoc = new SuperDoc({
  selector: "#editor",
  document: yourFile,
  modules: {
    trackChanges: { replacements: "independent" },
  },
});
With replacements: 'independent', editor.doc.trackChanges.list() returns one entry per revision and decide({ id }) resolves exactly that one side. The other half of the replacement stays in the document, still addressable by its own id — useful when you’re building a custom sidebar and want each revision as a separate row.

Document API

Use the Document API to list, read, and resolve tracked changes. It’s stable, typed, framework-agnostic, and works the same in the visual editor and headless mode.
const editor = superdoc.activeEditor;

// List every tracked change. In 'paired' mode a replacement is one entry;
// in 'independent' mode it's two (one insert, one delete).
const result = editor.doc.trackChanges.list();

for (const item of result.items) {
  console.log(item.id, item.type, item.author, item.excerpt);
}

// Fetch a single change by id.
const change = editor.doc.trackChanges.get({ id: result.items[0].id });

// Accept or reject by id.
editor.doc.trackChanges.decide({ decision: "accept", target: { id: change.id } });

// Accept or reject everything in the document.
editor.doc.trackChanges.decide({ decision: "accept", target: { scope: "all" } });
Every entry returned by list() and get() has this shape:
TrackChangeInfo
Object
list() accepts an optional query with limit, offset, and type ('insert' | 'delete' | 'format') for pagination and filtering. See the full reference:

Toggling tracked edits

Control recording via document mode:
superdoc.setDocumentMode("suggesting"); // record new edits as tracked changes
superdoc.setDocumentMode("editing"); // stop recording
superdoc.setDocumentMode("viewing"); // read-only

Change types

TypeMarkVisual
InsertiontrackInsertUnderlined in the reviewer’s color
DeletiontrackDeleteStrikethrough in the reviewer’s color
Format changetrackFormatRecords the before/after formatting on the run
Each mark carries id, author, authorEmail, date, and — for imports from Word — the original w:id as sourceId so you can round-trip revision provenance.

Events

Tracked-change events are delivered through the same onCommentsUpdate callback as comment events. The top-level type field tells them apart; filter on type === 'trackedChange' and read the flat payload.
onCommentsUpdate: (payload) => {
  if (payload.type !== "trackedChange") return;

  switch (payload.event) {
    case "add":
      // New tracked change created.
      break;
    case "update":
      // Existing tracked change updated (e.g. reply added, author edited).
      break;
    case "change-accepted":
      await markAccepted(payload.changeId);
      break;
    case "change-rejected":
      await markRejected(payload.changeId);
      break;
    case "resolved":
      // Tracked-change comment thread resolved.
      break;
  }
};

Payload fields

payload
Object
Events fire once per user action, not once per mark. A tracked replacement in paired mode emits one event with trackedChangeType: 'both'. To enumerate the current set of revisions, use editor.doc.trackChanges.list() — not the event stream.

Permissions

Accept and reject permissions are governed by the same permissionResolver used for comments. Return false from the resolver to block an action.
modules: {
  comments: {
    permissionResolver: ({ permission, trackedChange, currentUser, defaultDecision }) => {
      if (
        permission === "REJECT_OTHER" &&
        trackedChange?.attrs?.authorEmail !== currentUser?.email
      ) {
        return false;
      }
      return defaultDecision;
    },
  },
}
Tracked-change permission types:
PermissionDescription
RESOLVE_OWNAccept your own tracked changes
RESOLVE_OTHERAccept other users’ tracked changes
REJECT_OWNReject your own tracked changes
REJECT_OTHERReject other users’ tracked changes
See Comments → Permission resolver for the full list of permission types and resolver behavior.

Word import/export

Tracked changes round-trip through DOCX as native Word revisions. Import it. Edit it. Export it. Nothing lost.
// Export with revisions preserved. Word opens the file and shows the
// insertions and deletions under Review.
const blob = await superdoc.export();

// Accept all first, then export a clean copy.
superdoc.activeEditor.doc.trackChanges.decide({
  decision: "accept",
  target: { scope: "all" },
});
const cleanBlob = await superdoc.export();
Imported Word revisions preserve their original w:id values as wordRevisionIds on each TrackChangeInfo entry, so you can correlate SuperDoc revisions with the source document or an external review system.
Round-trip support today covers inserted run content (<w:ins>), deleted run content (<w:del>), and run-level format changes (<w:rPrChange>). Paragraph-level property changes, tracked table row and cell edits, and tracked moves are on the roadmap — they import as accepted content today.

Legacy editor commands

Deprecated. Use the Document API (editor.doc.trackChanges.list(), editor.doc.trackChanges.decide(...)) instead. The commands below remain available but will be removed in a future release.
These legacy commands live on superdoc.activeEditor.commands and predate the Document API. They’re still used by the built-in toolbar and a handful of keyboard shortcuts.

Enable and toggle

superdoc.activeEditor.commands.enableTrackChanges();
superdoc.activeEditor.commands.disableTrackChanges();
superdoc.activeEditor.commands.toggleTrackChanges();

Accept

superdoc.activeEditor.commands.acceptTrackedChangeBySelection();
superdoc.activeEditor.commands.acceptTrackedChangeById("change-123");
superdoc.activeEditor.commands.acceptTrackedChangesBetween(10, 50);
superdoc.activeEditor.commands.acceptAllTrackedChanges();
superdoc.activeEditor.commands.acceptTrackedChangeFromToolbar();

Reject

superdoc.activeEditor.commands.rejectTrackedChangeOnSelection();
superdoc.activeEditor.commands.rejectTrackedChangeById("change-123");
superdoc.activeEditor.commands.rejectTrackedChangesBetween(10, 50);
superdoc.activeEditor.commands.rejectAllTrackedChanges();
superdoc.activeEditor.commands.rejectTrackedChangeFromToolbar();

Insert a tracked change programmatically

superdoc.activeEditor.commands.insertTrackedChange({
  from: 10,
  to: 25,
  text: "replacement text",
  comment: "AI suggestion: improved wording",
});
options
Object
required

View modes

Temporarily render the document without applying revisions — useful for previewing the accepted or original state:
// Show the document as it was before tracked changes.
superdoc.activeEditor.commands.enableTrackChangesShowOriginal();
superdoc.activeEditor.commands.disableTrackChangesShowOriginal();
superdoc.activeEditor.commands.toggleTrackChangesShowOriginal();

// Show the document with all changes accepted.
superdoc.activeEditor.commands.enableTrackChangesShowFinal();
superdoc.activeEditor.commands.toggleTrackChangesShowFinal();

Full example

Track Changes Example

Runnable example: mode switching, accept and reject, comments sidebar, DOCX import and export.