Skip to main content
These are the recommended patterns for new integrations.

Plan with query.match, then apply with mutations

This is the recommended default for most apps: match first, preview, then apply.

Run multiple edits as one plan

When several changes should stay together, group them into one plan:

Quick search and single edit

For lightweight text edits, use query.match and apply against the canonical selection target returned by the match:

Find text and insert at position

Search for a heading (or any text) and insert a new paragraph relative to it:
The address from query.match is a BlockNodeAddress that works directly with create.paragraph, create.heading, and create.table. Use kind: 'before' to insert before the matched node instead. To insert as a tracked change, pass changeMode: 'tracked':
Use query.match (not find) for this workflow. query.match returns BlockNodeAddress objects that are directly compatible with mutation targets.
For direct single-operation calls, prefer item.target. For plans or multi-step edits, prefer item.handle.ref so every step reuses the same resolved match.

Chain table mutations with returned refs

For non-destructive table-targeted mutations, reuse result.table.nodeId from the previous success result. You do not need an intermediate find() between calls.
This handoff contract applies to table-targeted calls. Cell-targeted tables.setBorder, tables.clearBorder, tables.setShading, and tables.clearShading still return the targeted tableCell address today.

Build a selection explicitly with ranges.resolve

Use ranges.resolve when you already know the anchor points and want a transparent SelectionTarget plus a reusable mutation-ready ref:

Tracked-mode insert

Insert text as a tracked change so reviewers can accept or reject it:
The receipt includes a resolution with the resolved insertion point and inserted entries with tracked-change IDs.

Check capabilities before acting

Use capabilities() to branch on what the editor supports:

Cross-session block addressing

When you load a DOCX, close the editor, and load the same file again, sdBlockId values change: they’re regenerated on every open. For cross-session block targeting, use query.match addresses (NodeAddress with kind: 'block'), which carry DOCX-native paraId-derived IDs when available. This pattern is common in headless pipelines: extract block references in one session, then apply edits in another.
When the saved addresses are used in a browser-based viewer (RAG citations, search results, cross-references), pass the nodeId directly to scrollToElement:
scrollToElement also works with comment and tracked change IDs:
nodeId stability depends on the ID source. For DOCX-imported content, nodeId comes from paraId when available and is best-effort stable across loads. Runtime-created content is still not guaranteed stable across loads; many nodes use session-scoped editor identity, while some structures such as tables or table cells may expose deterministic fallback IDs instead of raw sdBlockId.
No ID is guaranteed to survive all Microsoft Word round-trips. Re-extract addresses after major external edits or transformations, since Word (or other tools) may rewrite paragraph IDs and SuperDoc may rewrite duplicate IDs on import.

Content extraction for RAG

doc.extract() returns all document content in one call: blocks with full text, comments, and tracked changes. Each item has a stable ID that works directly with scrollToElement.

RAG pipeline pattern

Extract content, chunk it, store the IDs, and navigate back on click:
All IDs from doc.extract() work directly with scrollToElement(): no conversion needed. For DOCX-imported content, block nodeId values are stable across sessions.

Read document counts

doc.info() returns a snapshot of current document statistics including word, character, paragraph, heading, table, image, comment, tracked-change, SDT-field, and list counts.
All counts reflect the current editor state, not OOXML metadata. They update naturally as the document changes.

Build a live counter in the browser

doc.info() is a snapshot read. To build a live counter, subscribe to document-change events and refresh counts in the handler: do not poll in a render loop. SuperEditor (raw editor):
SuperDoc (wrapper):

SDK usage

The SDKs do not expose browser event subscriptions. Call doc.info() at workflow boundaries: after opening a document, after a batch of mutations, or before saving.

Dry-run preview

Pass dryRun: true to validate an operation without applying it: