Editor Integration
Quick path: new to the API? Go from an API key to your first successful call in the Quickstart (~5 minutes), then come back here to wire up your editor. SuperDocs identifies every block-level element in your document with adata-chunk-id attribute. Your editor must preserve those attributes across the round-trip — HTML in, editor model, HTML back out — for surgical edits to work.
This guide covers attribute preservation in a rich-text editor. For rendering inline diff overlays inside the editor, see Human-in-the-Loop → Rendering diffs inline in your editor. For streaming events, see SSE Streaming. If your product doesn’t have an editor — your AI agent is the consumer of SuperDocs, or you’re running server-side / batch — see Agent Tool Integration or Server Integration instead.
Why this matters
Every SuperDocs response returns HTML like this:data-chunk-id values are how the AI references specific sections when it proposes edits. When your app sends the document back on the next turn, those same IDs must still be on the same blocks. If they aren’t, the AI’s “edit Section 2” request lands on the wrong block — or nothing at all.
The failure mode is silent. Most rich-text editors strip unknown HTML attributes by default when parsing input into their internal model, and/or don’t emit them when serialising back to HTML. Edits work intermittently on blocks whose tags happen to survive, and fail mysteriously on the rest. No error is thrown. A five-minute check now saves hours of debugging later.
The pattern, in plain English
Every editor integration follows the same three steps:- Parse incoming HTML from SuperDocs into your editor’s document model, preserving
data-chunk-idon every block-level node. - Serialise the editor model back to HTML, preserving the same
data-chunk-idattributes. - Render incoming updates.
document_sync.content(which arrives before the AI starts) can safely replace the whole document. For the AI’s results, prefer section-level application over whole-document replacement — see Applying AI updates safely below for why and how.
Working snippets
- ProseMirror
- TipTap
- Slate
- Lexical
- Quill
- CKEditor 5
Install the vanilla ProseMirror packages:Your schema needs to do two things:Use this
- Add
data-chunk-idas a preserved attribute on every standard block node (paragraph, heading, list, blockquote, code block, horizontal rule). - Register a wrapper node for
<div data-chunk-id="…">…</div>elements. SuperDocs uses these wrappers when a single chunk spans multiple block elements (e.g. a heading plus the paragraphs that follow it). If your schema has no node that matchesdiv[data-chunk-id], the parser will descend into the children, the wrapper’sdata-chunk-idwill be silently dropped, and any inline-diff or chunk-targeted edit referencing that ID will fail to render. The basic ProseMirror schema does not include a<div>node — you must add one.
schema when creating your EditorState. Use DOMParser.fromSchema(schema).parse(htmlElement) to load SuperDocs HTML and DOMSerializer.fromSchema(schema).serializeFragment(doc.content) to serialise it back out.Where this slots in: typically inside a React / Vue / Svelte component that mounts ProseMirror via new EditorView(domNode, { state }). Expose two methods on a ref — getHtml() and setHtml(html) — so the chat panel can read the current document before each send and write the updated HTML after each final event.Applying AI updates safely (without losing user edits)
The naive pattern — replace the whole editor document withfinal.updated_html on every turn — works for a demo and fails in production in two specific ways. We hit both building the SuperDocs web app; they’re worth designing out of your integration from day one.
Failure 1 — wiping concurrent user edits. AI turns take seconds to minutes. If the user keeps typing while the AI works and you then setContent the AI’s full document, everything they typed is silently gone. The fix is to apply changes per section: every result identifies exactly which chunks changed (document_changes, chunk_diffs in compact mode, or the per-change old_html/new_html in review mode), so you can replace only those data-chunk-id nodes and leave the rest of the document — including the user’s in-progress typing — untouched. If the user edited the same section the AI changed, don’t silently overwrite either version: keep the user’s version and offer the AI’s as a suggestion (or re-send the user’s version on the next turn). The user’s keystrokes should always win by default.
Protecting stored formatting on untouched sections. Editor serialisers rarely reproduce a document’s HTML byte-for-byte, and a purely cosmetic serialisation difference can look like a user edit. If your integration tracks which blocks the user actually modified, send that list as touched_chunk_ids alongside document_html on chat and save calls: a section not in the list whose text is unchanged keeps its stored formatting exactly, even if your serialiser emitted it differently. Sections in the list, and any section whose text changed, always take your submitted content. Omit the field for the default behaviour (any differing section is treated as an edit).
Failure 2 — double-applying in review mode. In ask_every_time mode, if your UI applies each change to the editor when the user accepts it, remember the job still finishes with a final event carrying the complete updated document. Applying accepted changes incrementally and then loading the final document re-applies everything — newly created sections get inserted twice. Pick one strategy and stick to it: either apply per-change on accept and ignore the final document, or keep the editor untouched during review and load only the final. Never both.
Revert and redo flow through the same path. When you rewind a session with POST /v1/sessions/{id}/revert, the response carries a per-document revert_changes map. Apply it section-by-section exactly like an AI result rather than reloading the whole document, so any in-progress typing survives and a clashed section can keep the user’s version and surface the other as an accept-able suggestion. Redo (POST /v1/sessions/{id}/redo) returns the same shape. Dry-run revert ("dry_run": true) returns the changes without committing, so you can preview it first. Full detail in Reverting without a whole-document swap.
Whole-document replacement remains fine for read-only/display integrations, or if you lock the editor while an AI turn is running.
If two of your surfaces (e.g. an editor tab and a server job, or two browser sessions) can edit the same document, the same section-level discipline applies across connections — poll for other sessions’ edits and re-apply them per section rather than reloading the whole document. See Concurrent & Cross-Session Editing.
Rendering visual content (diagrams and equations)
SuperDocs documents can contain diagrams (Mermaid), equations (LaTeX/KaTeX), drawings, and images. On the wire these arrive as HTML nodes that carry their source indata- attributes (e.g. the diagram’s text spec, the equation’s LaTeX) plus the node markup itself.
- Preserve the
data-attributes on round-trip — exactly the same rule (and the same silent failure mode) asdata-chunk-id. Most editor schemas and HTML sanitizers strip unknown attributes and<svg>by default; allow them through, or diagrams will degrade to empty blocks after one edit cycle. - Render client-side after mount. Use the standard libraries (Mermaid for diagrams, KaTeX for equations) to render the source into SVG/HTML once the node is in the DOM. Poll-free pattern: render on insert + on change of the source attribute.
- Render into a container your UI framework doesn’t manage. If your framework (React, Vue, etc.) believes it owns the rendered markup, its next reconciliation pass can silently wipe the out-of-band SVG you just produced — this exact bug shipped in our own web app before we caught it. Mount the rendered output inside an element the framework treats as opaque (a ref’d container you never re-render declaratively), not via an HTML-binding prop.
- You never need to rasterize for export —
POST /v1/documents/exportpre-renders diagrams and equations server-side in every format.
Out-of-flow parts (headers, footers, footnotes, comments)
A document with page headers, footers, footnote/endnote bodies, or reviewer comments carries them as ordinary blocks labeled with adata-part-type attribute (alongside their data-chunk-id). They hold out-of-flow content, so they aren’t body text:
- Preserve them like any other block. Same parse/serialise rule (and the same silent failure mode) as
data-chunk-id: keep thedata-part-typeattribute and the block intact on the round-trip. - Render them appropriately, or not at all. A footer block is not a trailing paragraph. If your UI has no header/footer affordance, it’s fine to hide these blocks; just keep them in the HTML you send back.
- Leaving one out never deletes it. The server treats an out-of-flow part’s absence from
document_htmlas normal (an editor view without the footer is the usual state), so a client that strips them can’t destroy them. To actually delete one, pass its chunk id in thedeleted_part_chunk_idsrequest field alongsidedocument_html. - Users edit them by chat like any other content (“fix the typo in footnote 3”). In review mode the proposal arrives as a normal entry in
proposed_change_batch, targeting the part’schunk_id.
Multi-document sessions in your editor
Sessions can hold several open documents (see the Multi-Document guide). If your product surfaces this, the editor-side pattern is:- Render tabs from
GET /v1/sessions/{id}/documents(stable insertion order; use thetitlefield each roster entry returns for the tab label). The roster is token-light by default — it returns metadata only and omits each document’s HTML body (null); passinclude_html=truewhen you actually need the content. - Switch tabs with the focus endpoint; pass
document_idon chat turns initiated from a specific tab. - Listen for
documents_changedto badge background tabs the AI touched, and to add a tab when an entry carriescreated: true(this fires in either approval mode for created documents). - If a document payload includes
page_setup, you can render true page geometry (size, orientation, margins) instead of a generic canvas.
Other editors and custom implementations
The principle is the same regardless of editor: preserve unknown HTML attributes on block-level elements across parse and serialise. Three things to verify in your editor’s documentation:- Does the parser strip unknown attributes by default? Most do. Look for “custom attributes” or “attribute preservation” in the docs.
- Does the serialiser emit them when converting back to HTML? If the parser accepted them, the serialiser usually does — but not always.
- Does the internal model store them on every block type you plan to use? Often there’s a schema or node definition that has to be extended per node type.
Verification test
Paste any SuperDocs response into your editor, read the HTML back out, and diff against the input. Everydata-chunk-id must survive.

