Tracked changes

Create tracked text edits, inspect revisions by story, accept or reject changes, and handle unsupported review markup.

Use Document.changeTrackingMode to create tracked text edits and RevisionCollection to review them. To accept or reject multiple changes, use RevisionCollection.resolve() for an API batch or the editor command for changes shown by a filter.

Create a runtime with Runtime and setup. Use context and document proxies inside runtime.run().

Create a tracked replacement

Configure a nonempty author when creating the runtime. For browser tracked writes, install the Pro review module on an editable editor. Then enable TrackMineOnly and use an ordinary range edit:

await runtime.run(async (context) => {
  const matches = context.document.body.search('within 7 days', {
    matchCase: true,
  });
  matches.load('items');
  await context.sync();
  if (matches.items.length !== 1) throw new Error('Choose one deadline');
  context.document.changeTrackingMode = 'TrackMineOnly';
  matches.items[0]!.insertText('within 30 days', 'Replace');
  await context.sync();
});

Use insertText(text, 'Before' | 'After') for insertion suggestions. Use range.delete() or range.clear() for deletion suggestions. Inserted ranges require sync before dependent use. Mode assignments and edits commit together; a failed batch preserves the previous mode and document.

ChangeTrackingMode valueRuntime behavior
OffInitial mode; ordinary permanent edits
TrackMineOnlyText and property tracking for this runtime with an explicit author; browser writes require the Pro review module
TrackAllFails with NotSupported

The runtime's tracking setting does not change the browser editor UI mode. Read document.changeTrackingMode only after loading it. The mode persists across runs on that runtime. It does not change peers' tracking settings or save a document-wide tracking policy. Set Off explicitly only when your application intends permanent edits.

Supported tracked edits

TrackMineOnly supports these edits:

EditSupported behavior
Range textInsert, replace, or delete text; adjacent sibling paragraphs work outside collaboration
Font and paragraph formattingRecord property revisions, including paragraph styles
Paragraph insertionTrack text and paragraph marks; reject removes both
ListsTrack creation, membership, and level changes; configure newly proposed definitions
TablesTrack complete insertion, cell values, row additions, and partial row deletions
Content controlsWrap nonempty ordinary text in PlainText, RichText, or DatePicker controls outside collaboration

Tracked ranges refuse table and wrapper boundaries. Collaborative tracked ranges must remain within one paragraph. Targets that touch foreign pending revisions refuse. Insertions can continue the runtime author's text, paragraph, and formatting proposals.

Appends can follow the author's pending deletion at the paragraph tail. Other deletion boundaries refuse. Structural and move revisions also block continuation.

A pending row insertion or deletion blocks tracked text edits throughout that row, including nested tables. An author can configure a complete proposed table while it has no foreign revisions. Existing table properties and columns require permanent edits. Tracked table and cell value replacement refuse in collaboration. See Tables and cells for row deletion limits.

Accept keeps a proposed content control. Reject restores the original formatted text. The author can set the pending control's tag and title. Empty ranges, existing review markup, and other control structure changes refuse.

Established list definitions, pictures, page setup, and unsupported structural edits refuse. Comments and supported revision decisions remain available.

Never turn tracking off automatically to make an unsupported operation succeed. Report the limit or ask your application to choose a separately authorized permanent edit. For a multi-stage review, each successful sync becomes a separately committed suggestion. A later refusal does not roll back earlier suggestions.

Choose the revision text view

The runtime option revisionTextView affects ordinary text loads and search. allMarkup is the default. original retains pending deletions and hides pending insertions. This is a DocxEditor host option, not a Word document-model property. It does not accept or reject revisions.

Use plain text and explicit revision information when preparing a model prompt. Do not pass live proxies into a model or job queue. See Runtime and setup.

Inspect revisions by story

Document.revisions addresses the main story. Body.revisions addresses that body's story, including a header, footer, or note. It never merges all stories into one collection.

Load collection membership, then load author, date, and type:

const pending = await runtime.run(async (context) => {
  const revisions = context.document.revisions;
  revisions.load('items');
  await context.sync();
  for (const revision of revisions.items) {
    revision.load(['author', 'date', 'type']);
  }
  await context.sync();
  return revisions.items.map((revision) => ({
    author: revision.author,
    date: revision.date?.toISOString() ?? null,
    type: revision.type,
  }));
});

Revision.date is Date | null; narrow missing or invalid dates before using date methods. Revision.type uses the exported RevisionType vocabulary. Revision.range returns a range for the addressed revision. Resolve the range before reading or selecting its content. RevisionCollection exposes items, resolve(), acceptAll(), and rejectAll(); it has no first-item accessor.

Resolve a batch of changes

Use RevisionCollection.resolve(action, revisions?) to accept or reject supported changes in one story. Pass 'accept' to keep the changes or 'reject' to undo them. Unsupported changes remain pending.

API batches don't inherit editor filters. Pass revision objects from the same request context and story to select changes. For example, accept Ada's changes:

await runtime.run(async (context) => {
  const revisions = context.document.revisions;
  revisions.load('items');
  await context.sync();
  for (const revision of revisions.items) {
    revision.load('author');
  }
  await context.sync();

  const selected = revisions.items.filter((revision) => revision.author === 'Ada');
  const result = revisions.resolve('accept', selected);
  await context.sync();

  const { resolved, skipped, remaining } = result.value;
  console.log({ resolved: resolved.length, skipped, remaining });
});

Read result.value after context.sync(). Changes commit in one transaction and one undo step. Browser writes require the Pro review module and an editable document.

To process the whole story, omit the selection. This includes complete tracked rows and reports unsupported changes omitted from items:

await runtime.run(async (context) => {
  const result = context.document.revisions.resolve('reject');
  await context.sync();
  for (const skipped of result.value.skipped) {
    console.log(skipped.revision?.author, skipped.reason);
  }
});

Keep resolve() as the only write in its sync batch; reads can share the batch. Use a separate sync for each story. Protection, content control locks, and collaboration permissions still apply. If the transaction fails, no changes commit and the result remains unloaded.

Interpret the result

FieldMeaning
resolvedResolved decisions, with their key, author, part name, and revision kind.
skippedRequested decisions left pending, with a key, reason, and revision metadata when known.
remainingNumber of pending decisions in the story, including excluded and unsupported changes.

A decision can contain several XML markers, such as both halves of a replacement. A successful batch can leave changes pending:

Skip reasonNext step
unsupported-revisionResolve the unsupported structure in Word. Independent supported changes can still resolve.
incomplete-groupReview and select all related decisions together.
retained-structureA row remains to preserve nested table content. Other selected text changes may have applied; inspect the remaining decision.
unknown-revisionReload the collection; the target no longer exists.

Nested changes, move pairs, tracked rows, and paragraph marks with property changes can form inseparable groups. If a group contains an excluded or unsupported decision, the batch skips the group, even if the action could preserve part of it. It doesn't expand your selection. A retained-structure result is different: supported text changes in the selected row can resolve while its structural marker stays pending to preserve the nested table.

Pass an empty array to select nothing. Duplicate objects resolve once; stale objects produce unknown-revision. Objects from another context or story fail validation.

Use revision objects instead of raw OOXML w:id values, which Word can reuse. Treat result keys as opaque identifiers for the open document. Reload revisions after saving and reopening a file.

Resolve changes shown in the editor

The editor's bulk controls honor author visibility and the tracked-changes filter, including offscreen changes in supported stories. Mounted cards and activation exclusions don't affect the selection. Supported changes resolve in one undo step; unsupported changes remain pending.

For example, accept changes shown by an author filter:

editor.setTrackedChangesFilter((revision) => revision.author === 'Ada');
const result = editor.exec({
  type: 'resolveAllReviewChanges',
  action: 'accept',
});
if (result.ok) {
  console.log(result.revisions);
}

To select an explicit batch, pass keys from editor.getReviewItems(). Keys override the active filter:

const keys = editor
  .getReviewItems({ placement: false })
  .filter((item) => item.kind === 'revision' && item.author === 'Ada')
  .map((item) => item.key);

const result = editor.exec({
  type: 'resolveAllReviewChanges',
  action: 'reject',
  keys,
});

To include all authors across supported stories, pass scope: 'document'. To require every selected change to resolve, pass unsupported: 'fail'. The defaults are scope: 'visible' and unsupported: 'skip'.

The result has the same fields as an API batch; remaining counts pending decisions across the document, including hidden changes. If no eligible changes exist, editor.can() returns a refusal and editor.exec() adds no undo step.

Require every change to resolve

Use Revision.accept() or Revision.reject() for one supported revision. Use RevisionCollection.acceptAll() or rejectAll() for a strict story-wide batch:

await runtime.run(async (context) => {
  context.document.revisions.acceptAll();
  await context.sync();
});

These collection methods ignore editor filters and fail the entire sync if any revision is unsupported. They include complete tracked rows omitted from items. An empty collection succeeds and keeps document content unchanged.

Malformed content revision wrappers refuse resolution and retain their content. This includes w:ins or w:del wrappers containing paragraphs.

If a strict batch fails with NotImplemented, use resolve() to process supported changes and report what remains. For StaleDocument, start a fresh run and reload revisions before deciding again.

Update existing integrations

Existing acceptAll() and rejectAll() calls keep their behavior. To support partial batches or an explicit selection, use resolve().

The editor's Accept all changes shown and Reject all changes shown controls honor filters and skip unsupported groups. To keep the previous editor command behavior, pass scope: 'document' and unsupported: 'fail'.

Try the demo

From a repository checkout, run:

bun install
bun run dev:react

Open http://localhost:5173/?bulkReview=1&fixture=bulk-review.docx. Filter by author or select individual changes, then accept or reject them. Compare the filtered editor controls with the main-story API controls. Use Undo, Redo, and Save and reopen to check that excluded and unsupported changes remain pending.

To run the browser tests:

bunx playwright test --config e2e/editor-smoke.config.ts bulk-review.interaction.spec.ts

Next steps

See the server agent guide, Collaboration, and Batching, loading, and errors.