Handle lifecycle and events
Understand when the Editor is ready, what counts as an edit, and when to release it.
Your application needs to know when the document is usable, when it changes, and when to release the Editor. Add those states to the Quickstart before connecting storage or custom controls.
Follow the lifecycle
Select a stage to see its signal and application state. This is a simulated preview, not a live Editor.
new SuperDoc()Show a loading state
Keep document actions disabled while the DOCX opens.
const superdoc = new SuperDoc({ selector: '#editor', document: '/sample.docx',});Connect the signals
In the Vanilla Quickstart, replace the export button and mount point with this markup. Keep the script tag:
<button id="export-docx" type="button" disabled>Export DOCX</button>
<output id="editor-status" aria-live="polite">Opening…</output>
<div id="editor"></div>Replace src/main.ts with the example below. Keep the styles and /sample.docx; no backend is needed.
Copy any user or mode options you chose on the previous pages into this configuration.
import { SuperDoc } from 'superdoc';
import 'superdoc/style.css';
function requireElement<ElementType extends Element>(selector: string) {
const element = document.querySelector<ElementType>(selector);
if (!element) throw new Error(`${selector} not found.`);
return element;
}
const status = requireElement<HTMLOutputElement>('#editor-status');
const exportButton = requireElement<HTMLButtonElement>('#export-docx');
let isReady = false;
let hasUnsavedChanges = false;
let isExporting = false;
let isUnmounted = false;
function showLoadError(error: unknown) {
console.error('SuperDoc error', error);
if (isReady || isUnmounted) return;
status.value = 'Could not open the document';
exportButton.disabled = true;
}
const superdoc = new SuperDoc({
selector: '#editor',
document: '/sample.docx',
onReady: () => {
if (isUnmounted) return;
isReady = true;
status.value = 'Ready';
exportButton.disabled = isExporting;
},
onEditorUpdate: () => {
if (isUnmounted) return;
hasUnsavedChanges = true;
status.value = 'Unsaved changes';
},
onContentError: ({ error }) => showLoadError(error),
onException: ({ error }) => showLoadError(error),
});
async function exportDocument(): Promise<void> {
if (!isReady || isExporting || isUnmounted) return;
isExporting = true;
exportButton.disabled = true;
try {
await superdoc.export({ exportedName: 'sample-edited' });
if (!isUnmounted) status.value = hasUnsavedChanges ? 'Unsaved changes' : 'Ready';
} catch (error) {
if (!isUnmounted) status.value = 'Export failed. Try again.';
console.error('Could not export the document.', error);
} finally {
isExporting = false;
if (!isUnmounted) exportButton.disabled = !isReady;
}
}
exportButton.addEventListener('click', exportDocument);
export function unmountEditor(): void {
isUnmounted = true;
exportButton.disabled = true;
exportButton.removeEventListener('click', exportDocument);
superdoc.destroy();
}
onReady means the document is available for queries and export. onEditorUpdate reports document edits, not cursor
movement or pagination. Downloading a copy does not save changes to your application, so it does not clear the
Unsaved changes status.
In a collaborative room, onEditorUpdate also fires when another collaborator's change reaches this editor. The event's
isRemote field is true for those changes and false for edits, undo, and redo made here. Skip isRemote events
when the handler writes back to your application or marks this user's changes as unsaved. A remote event always
reports surface: 'body' because the room does not say which part of the document changed.
Choose where to listen
The SuperDoc instance reports the instance: its lifecycle, failures, the document lock, the collaboration session,
and the built-in UI's own chrome. Everything about one area of the document or the view belongs to that area of
superdoc.ui, where getSnapshot() and observe() report state, onChange()
reports committed changes, and onX() reports an interaction.
| Your question | Where to listen |
|---|---|
| Is the document open, and did opening it fail? | onReady, onException |
| Is the document locked, and is collaboration still connected? | onLocked, onCollaborationConnectionChange |
| Did the built-in comments list appear or disappear? | onCommentsListChange |
| What changed in the document? | superdoc.ui.document.onChange() |
| What is the zoom, the mode, or the undo depth right now? | superdoc.ui.zoom, superdoc.ui.document, superdoc.ui.history |
An instance event is deprecated once its superdoc.ui replacement reports everything the event reported. Deprecated
events keep working until SuperDoc 3.0.
| Deprecated event | Replacement |
|---|---|
zoomChange and onZoomChange | superdoc.ui.zoom.observe() |
document-mode-change | superdoc.ui.document.observe() |
list-definitions-change and onListDefinitionsChange | onEditorUpdate and doc.lists.list() |
The document snapshot reports the mode. SuperDoc 2 never emits list-definitions-change.
history-change stays on the instance: it reports every open document with its documentId, while
superdoc.ui.history follows the active document.
Handle rejected edits
The browser Editor reports a rejected keyboard edit through onException (or the exception event),
with code: 'edit-rejected'. Your application decides whether and how to present a message. SuperDoc
adds no error message to the document UI. Each independent rejected attempt produces its own event;
a proven no-op at a document or body/table boundary produces none.
The payload's error is unknown: narrow it to an Error before reading message or cause.
The optional error.cause contains diagnostic codes such as failureSource, reason, inputKind,
and editableCommandKind. failureSource: 'shell' identifies a host dispatch rejection, while
'receipt' identifies a Document API mutation failure. These values identify the originating layer,
not severity. Cause fields and reason values are diagnostic details, not a typed, exhaustive public
contract; tolerate absent fields and unfamiliar reasons. Use the exception code for application routing
and avoid treating a diagnostic reason as a permanent capability check.
The normalized cause excludes raw document text and receipt messages. Review other error details before sending them to your logging or support system.
For a rejected edit inside a locked content control, the callback does not guarantee a lock-specific reason or rejecting-control ID. Selection and content-control snapshots can have different readiness states in the same callback. Use the field-state and cached-context guidance for ready active IDs and bounded synchronous lookup, including shared editing. Treat cached field context as context rather than evidence of the rejection's cause.
Release what you own
Call unmountEditor() when the owning route or component unmounts. It removes the button listener and calls
destroy(). The React Quickstart's SuperDocEditor component handles its own Editor cleanup.
For a temporary panel, keep the function a superdoc.ui subscription returns and call it when the panel closes. An
instance event uses on() and off() with the same function. This fragment assumes the ready superdoc instance
from the example above:
const stopZoom = superdoc.ui.zoom.observe(({ value }) => {
console.log('Zoom:', value);
});
// When the panel closes:
stopZoom();Check the flow
Reload the page. Export DOCX should stay disabled until the document opens. Edit the effective date, then export.
The browser should download sample-edited.docx, while the status remains Unsaved changes.
Temporarily change /sample.docx to a missing URL. The page should show a load error and keep Export DOCX disabled.
Go deeper
- Load and save documents connects the Editor to your backend storage.
- Configure the Editor lists all startup callbacks and their payload types.
Export recent Interaction History
The browser Editor keeps a bounded Interaction History in memory by default. When an edit looks wrong, your application can request a snapshot without waiting for an exception:
const history = superdoc.diagnostics.getSnapshot();
const report = JSON.stringify(history);
// Let the customer review and share the report through your bug-report flow.
superdoc.diagnostics.clear();The history records recent input attempts, commands, mutation receipts, selection changes, render progress,
and lifecycle failures. Collaboration sessions also record remote changes observed by this browser.
A snapshot is detached from the running recorder; capturing it does not query the worker or document.
You can also request one inside onException.
Configure retention when creating the Editor:
const superdoc = new SuperDoc({
selector: '#editor',
diagnostics: {
history: {
enabled: true,
maxEvents: 500,
maxBytes: 1_048_576,
captureContent: false,
},
},
});These are the defaults. The oldest events are removed when either limit is reached. maxEvents accepts
1–10,000; maxBytes accepts 1–16,777,216 and measures retained serialized UTF-16 payloads, excluding object
overhead. Invalid limits use the defaults. Individual events and nested payloads are also capped; snapshots
report eviction, truncation, and capture-failure counts. Set enabled: false to disable capture.
By default, the recorder selects metadata fields and excludes document text and error messages.
captureContent: true allows bounded text carried by observed events and API calls; treat those reports
as document content. Metadata can include document and transaction identifiers, so review reports before sharing.
This recorder does not upload or persist reports. Capture stops when the instance is destroyed; the history
belongs to that instance and does not survive a page reload. Capture failures are isolated from editor operations.
History is diagnostic evidence, not a complete replay or audit log. Selection events may be coalesced, older events may be evicted, and each collaboration peer records its own observation order.