Document refresh API
Display updated DOCX files, preserve scroll, and present recent changes in React and Vue.
Use createDocumentRefresh(editor) to display complete DOCX files that your server returns. The controller keeps the editor instance, subscriptions, and scroll position. Your application receives each result; the editor does not watch files or URLs.
The controller is a host integration API, separate from the Office.js-shaped automation model. If your agent can issue document edits, use Revisions instead.
API overview
Call createDocumentRefresh(editor) after the editor mounts. Repeated calls for one editor return the same controller. Import it from your adapter or @docx-editor.dev/core/editor.
| Method | Purpose |
|---|---|
capture() | Capture the current bytes and revision. Returns a submission. |
applyUpdate(update) | Accept a complete DOCX for that submission and sequence. Returns a success or refusal result. |
finish(submission) | Close the submission when the job ends. Accepted content and change navigation stay. |
cancel() | Invalidate pending results. It does not cancel your network request. |
highlightChanges(options?) | Highlight available changes. Returns the selected available change count, including offscreen locations, or 0 during replacement. |
clearHighlights(options?) | Dismiss highlights, with or without a fade. |
navigateToChange(id, options?) | Reveal a change location. Returns whether it is available. |
snapshot(), subscribe(listener), onResult(listener) | Read cached status, observe changes, and receive every result, including refusals. Both listeners return an unsubscribe function. |
recover(), recoveryBytes() | Retry restoration after a failed replacement, or get the previous bytes. |
The flow is capture(), then your server job, then applyUpdate(), then finish().
Apply an updated DOCX
Capture the document, send its bytes to your backend, and apply the response:
import { createDocumentRefresh } from '@docx-editor.dev/react';
const refresh = createDocumentRefresh(editor);
const submission = await refresh.capture();
try {
const response = await fetch('/api/update-document', {
method: 'POST',
body: submission.bytes,
});
if (!response.ok) throw new Error('Processing failed');
const result = await refresh.applyUpdate({
submission,
sequence: 1,
bytes: await response.arrayBuffer(),
});
if (result.ok) {
refresh.highlightChanges();
} else {
showRefreshError(result.code);
}
} finally {
refresh.finish(submission);
}Get editor from useDocxEditor() inside the editor root. React returns an instance or null; Vue returns a shallow ref.
capture()commits buffered input and form input, and waits for text composition to end.- Pass the same
submissionobject toapplyUpdate(). A copied, superseded, or other editor's token cannot authorize replacement. - Any edit after capture makes
applyUpdate()returnlocal-edits, even after undo. Capture again; do not replay the old result. applyUpdate()resolves after the replacement lays out its opening pages and accepts clicks.
Receive cumulative results
The processor assigns each result a nonnegative integer sequence. Each file holds the complete cumulative output for the submission:
const submission = await refresh.capture();
try {
for await (const update of receiveProcessorUpdates(submission.bytes)) {
const result = await refresh.applyUpdate({
submission,
sequence: update.sequence,
bytes: update.bytes,
changes: update.changes,
failures: update.failedOperationIds,
});
if (!result.ok) {
if (result.code === 'out-of-order') continue;
showRefreshError(result.code);
break;
}
showChanges(result.changes);
showPartialFailures(result.failures);
}
} finally {
refresh.finish(submission);
}update field | Meaning |
|---|---|
submission | The object that capture() returned. |
sequence | Processor order, never network arrival order. Duplicate or older sequences return out-of-order. Replacements run one at a time, and only a successful load advances the accepted sequence and revision baseline. |
bytes | The complete DOCX, as ArrayBuffer or Uint8Array. |
changes | Optional change metadata. See Provide change locations. |
failures | Optional failed processor operation IDs. The controller reports them and never retries them. |
Do not start processing from every change event. Replacement events have source: 'refresh' or source: 'recovery', loads have source: 'load', and user edits omit source:
editor.on('change', (change) => {
if (change.source) return;
scheduleProcessing();
});Provide change locations
A plain file does not identify its changes, and the controller does not compare files. Without metadata or tracked revisions, the result reports changeInformation: 'unavailable'. Supply metadata with stable IDs across cumulative results:
const result = await refresh.applyUpdate({
submission,
sequence: 2,
bytes: updatedBytes,
changes: [
{
id: 'delivery-date',
description: 'Changed the delivery date',
location: { paragraphId: '00A12B34', start: 10, end: 20, text: 'October 12' },
},
{ id: 'removed-clause', description: 'Removed a clause', unavailableReason: 'deleted' },
{ id: 'footer-version', description: 'Updated the footer version' },
],
});| Location rule | Detail |
|---|---|
| Selector | Exactly one of paragraphId (the w14:paraId) or paragraphIndex (zero-based body order, including table paragraphs). |
| Paragraph ID | Eight hex digits from 00000001 through 7FFFFFFF, case-insensitive. Loading normalizes invalid or duplicate IDs, so use IDs from the returned file. |
| Offsets | UTF-16 code units, a nonempty range within one body paragraph. |
text | Must equal the returned text between start and end. |
description | Up to 1000 characters, returned unchanged. Render it as text. |
Invalid location metadata does not reject valid document bytes. That change gets a non-available status and a diagnostic.code, with the requested diagnostic.location:
| Diagnostic code | Meaning |
|---|---|
invalid-selector | Both selectors, neither, or an invalid paragraph index. |
invalid-paragraph-id | The ID is outside the supported hex range. |
paragraph-not-found | The returned document lacks the paragraph. |
ambiguous-paragraph-id | Several paragraphs match the ID. |
invalid-offsets | The offsets do not form a nonempty range in the paragraph. |
text-mismatch | The returned text differs from text. |
not-rendered | The paragraph has no layout position. |
missing-location | The change has no location. |
deleted | The deleted content has no surviving location. |
unsupported-story | A revision is outside the body. |
multiple-paragraphs | A revision spans several paragraphs. |
If you omit changes, a registered review module supplies tracked revision locations. Revisions present at capture do not count as new. The review module requires the EigenPal Pro License.
Present changes
Highlight new available changes after a successful applyUpdate(), then navigate:
const target = refresh
.snapshot()
.changes.find((change) => change.isNew && change.status === 'available');
if (target && refresh.navigateToChange(target.id, { block: 'centerIfNeeded' })) {
refresh.highlightChanges({ changeIds: [target.id], timeoutMs: 3000 });
}isNewmarks new IDs, changed text, and changed descriptions within one submission.- Locations stay valid until the next accepted result or local edit.
- Highlights change presentation only. They add no saved bytes and no undo entries.
- Keep a text change list beside the document, because color alone does not explain a change.
This simulation shows highlights, navigation, and refusals with sample text:
Customize highlights and motion
RefreshHighlightOptions extends AnchorHighlightOptions. The shared color, border, padding, timing, and animation options are listed in Highlight a paragraph reference. Refresh differs in these options:
| Option | Default | Meaning |
|---|---|---|
changeIds | Omitted | Select these IDs, including earlier changes. Overrides includePrevious. An empty array dismisses highlights. At most 10000 nonempty string IDs. |
includePrevious | false | Also highlight earlier changes in the latest cumulative result, not only new ones. |
color | var(--doc-refresh-highlight-color) | The refresh theme token, separate from the anchor token. |
Each call starts from the defaults, so reuse one options object. Repeated calls reset the timeout without replaying the entrance. To replay it, call clearHighlights({ animation: false }) first.
highlightChanges()validates options even when change locations are stale or unavailable.animation.exitDurationMsdefaults todurationMsand accepts0through10000milliseconds.- Expiration removes the styling. Change metadata and navigation stay available.
- Without overrides,
clearHighlights()uses the last requested exit duration and easing.snapshot().highlightsVisiblebecomesfalsewhen dismissal starts, before the exit animation ends.
Control change navigation
navigateToChange() takes NavigateToChangeOptions:
| Option | Default | Meaning |
|---|---|---|
block | 'center' | 'start', 'center', 'centerIfNeeded', or 'nearest'. |
behavior | 'instant' | 'smooth' for an explicit animated action. |
offsetPx | 24 | Nonnegative edge padding for 'start' and 'nearest', in CSS pixels. |
focus | false | Move focus and the caret to the change start. |
Navigation returns false for an unavailable location or during replacement. It does not wait for smooth scrolling. Invalid modes throw TypeError; invalid padding throws RangeError.
Handle refusals and recovery
applyUpdate() returns { ok: false, code } with a RefreshFailureCode. capture() rejects with DocumentRefreshError, whose code is a RefreshFailureCode. Handle codes, not messages:
| Code | Situation | Action |
|---|---|---|
local-edits | The user edited after capture, even with undo. | Capture again and start a new job. |
out-of-order | A duplicate or older sequence arrived. | Discard the result. |
superseded | The result belongs to an inactive submission, or arrived after cancellation. | Discard it. |
document-changed | The document changed during replacement. | Discard it. |
cancelled | Cancellation interrupted a replacement. | Stop the transport. |
collaboration | A collaboration session is attached, even if disconnected. | Use document edits through collaboration instead. |
unavailable | No mounted document, or the document is still opening. | Retry after the editor mounts. |
busy | A replacement is running. | Wait for it to finish. |
invalid-result | The update object is malformed, such as a change with wrong field types. | Fix the update. |
invalid-document | The bytes are not a valid DOCX. The visible document stays. | Report the processor failure. |
input-failed | Pending input or serialization failed. | Report the failure. |
load-failed | A valid file failed to mount. Check recovered for restoration. | Report the failure. |
recovery-failed | Mounting and restoration both failed. | Offer recovery. |
Do not retry a refused result blindly. Connect your cancel control to both the transport and the controller, because cancellation does not reverse accepted files:
const transport = new AbortController();
// Pass transport.signal to your fetch calls.
function cancelProcessing() {
transport.abort();
refresh.cancel();
}When refresh.snapshot().recoveryAvailable is true, offer two controls:
await refresh.recover()reloads the previous document, resets selection and undo history, and resolvesfalseon failure.refresh.recoveryBytes()returns the document from before the failed replacement, for download.
Recovery bytes are not an undo archive for accepted files.
Limits
- Each accepted file replaces the whole document and resets selection, undo history, and some review state.
- Refresh refuses collaborative sessions, including disconnected ones.
- Scroll uses pixel offsets. A shorter file clamps the offset; neither case keeps the same subject in view.
- The controller does not compare document meaning or reject unrelated content. Check document and job identity in your application before
applyUpdate(). - An ordinary
editor.load()invalidates pending submissions and recovery. - Highlight numbers must be finite.
padding,borderRadius, andborderWidthmust be nonnegative.timeoutMsaccepts0through2147483647milliseconds, ornull. - Reduced-motion preferences cap fades at 125 milliseconds and make smooth navigation instant.