Removed in v2
Every v1 import and editor internal that no longer works in SuperDoc v2, and what replaces it.
Upgrading breaks three things, and they surface at different times. Removed subpaths always fail module resolution. Removed root exports fail the build under ESM and TypeScript, but a CommonJS require binds them to undefined and fails later at the call site. Configuration and editor internals that v2 no longer honors fail only once the editor is running, often without naming SuperDoc at all.
Work through them in that order. The import failures are the loudest, and fixing them tells you where the rest of the work is.
This page describes [email protected] compared against [email protected]. A machine-readable version is available at /migration/v1-to-v2.json.
How to read the Migration column
- Mechanical — A direct substitution with equivalent behavior. Safe to apply mechanically, and required to be backed by a compiled fixture before it carries this label.
- Redesign — A replacement exists, but the semantics differ. Read the replacement and re-verify the behavior; do not apply it as a find-and-replace.
- No equivalent — No v2 equivalent. The capability must be removed or rebuilt outside SuperDoc.
Nothing here is currently classified as Mechanical, so this page is an inventory and a decision aid, not a codemod. Do not apply any of it as an automated find-and-replace.
Search the reference
75 entries. Search or filter to narrow them.
Every entry is listed in the tables below. Search a v1 name, or filter, to jump straight to one.
Every entry is also listed in full below, grouped by when the failure surfaces. The tables are the canonical form: they work without JavaScript and are what llms.txt and search engines read.
Your application no longer imports
Removed package subpaths
v1 published 10 code subpaths. v2 publishes 3: superdoc/collaboration-upgrade-engine, superdoc/ui, superdoc/ui/react.
| v1 | v2 | Migration | Notes |
|---|---|---|---|
superdoc/types | superdoc | Redesign | Not a path rename. v1 re-exported 116 names here, almost all of them ProseMirror and schema types, and none of them exist on the v2 root. Integration types such as Config, DocumentMode, User, and DocumentApi are exported from superdoc; types describing ProseMirror nodes, marks, commands, and transactions have no v2 equivalent because the model they described is gone. Read more |
superdoc/super-editor | superdoc.activeEditor | Redesign | v2 has no importable editor class. Reach the active document through the SuperDoc instance and its Document API facade. Read more |
superdoc/converter | None | No equivalent | v2 owns DOCX parsing and serialization internally. Load documents through document and produce output through export(). Read more |
superdoc/docx-zipper | None | No equivalent | No public archive surface in v2. Use the supported load and export workflow. |
superdoc/file-zipper | None | No equivalent | No public archive surface in v2. Use the supported load and export workflow. |
superdoc/headless-toolbar | superdoc.ui | Redesign | superdoc.ui is a getter that lazily creates and owns the controller. It returns BorrowedSuperDocUI, which omits destroy() because superdoc.destroy() owns teardown. Call createSuperDocUI({ superdoc }) from superdoc/ui only when you want an independently owned controller to dispose yourself. Read more |
superdoc/headless-toolbar/react | superdoc/ui/react | Redesign | Hooks and providers moved and now bind to the instance-owned controller. Read more |
superdoc/headless-toolbar/vue | superdoc.ui | Redesign | v2 ships no Vue-specific bindings. Build on the framework-agnostic controller. Read more |
A removed subpath fails module resolution in every module system, because it is absent from the package's exports map. Do not work around it by importing an internal package: names such as @superdoc/v2-host and @superdoc/document-api-v2-adapter are implementation details with no compatibility guarantee.
Removed root exports
v1 exported 41 runtime values from the package root. v2 exports 10: BlankDOCX, DOCX, HTML, PDF, SuperDoc, buildTheme, compareVersions, createTheme, defineSuperDocExtension, getFileObject.
Everything below was removed. defineSuperDocExtension is the one value v2 adds.
Editor internals
| v1 | v2 | Migration | Notes |
|---|---|---|---|
Editor | superdoc.activeEditor | Redesign | v2 exposes no editor constructor. Mount through SuperDoc and read the active document via its Document API facade. Read more |
PresentationEditor | None | No equivalent | v2 owns rendering internally. There is no public presentation-editor surface. |
SuperEditor | SuperDoc | Redesign | The mounting capability survives, the component does not. v1 rendered a Vue component taking fileSource, documentId, state, and options props; v2 mounts imperatively with new SuperDoc({ selector, document }). A React or Vue wrapper is now application code around that instance. Read more |
SuperInput | None | No equivalent | v2 ships no importable single-line input component, and no v2 surface is a drop-in equivalent. Build the control in your application. |
superEditorHelpers | superdoc.activeEditor.doc | Redesign | Helper bags are replaced by explicit Document API operations. Read more |
trackChangesHelpers | superdoc.activeEditor.doc.trackChanges | Redesign | Helper bag replaced by explicit Document API operations for listing, navigating, and deciding tracked changes. Read more |
fieldAnnotationHelpers | superdoc.activeEditor.doc | Redesign | Field and annotation work moves to Document API operations. |
AnnotatorHelpers | superdoc.activeEditor.doc | Redesign | Helper bag replaced by explicit Document API operations. Field and annotation work goes through doc, not a helper namespace. |
SectionHelpers | superdoc.activeEditor.doc.sections | Redesign | Verify coverage before migrating: linked-section workflows may not have full v2 equivalents. |
getAllowedImageDimensions | None | No equivalent | No public sizing helper in v2. Compute the constraint in your application, or let the engine apply its own bounds on insert. |
CommentsPluginKey | superdoc.activeEditor.doc.comments | Redesign | v2 exposes no ProseMirror plugin keys. Use the Document API comments operations. Read more |
TrackChangesBasePluginKey | superdoc.activeEditor.doc.trackChanges | Redesign | v2 exposes no ProseMirror plugin keys. Read more |
Converter and archives
| v1 | v2 | Migration | Notes |
|---|---|---|---|
SuperConverter | None | No equivalent | DOCX conversion is internal to the v2 engine. Load through document and produce output through export(). Read more |
DocxZipper | None | No equivalent | No public archive surface in v2. |
createZip | None | No equivalent | No public archive surface in v2. |
Custom UI
| v1 | v2 | Migration | Notes |
|---|---|---|---|
SuperToolbar | config.toolbar | Redesign | For the built-in toolbar, pass a container through toolbar in the config. For a custom toolbar, drive superdoc.ui. Read more |
Toolbar | config.toolbar | Redesign | The built-in toolbar is configured, not imported and rendered by the consumer. Read more |
ContextMenu | None | No equivalent | getV2FeatureMatrix() reports shell.context-menu as not-shipped: the v2 shell disables the legacy context menu. A modules.contextMenu config field exists but no v2 runtime renders it, so it is not a working replacement. Build an application-owned menu on superdoc.ui if you need one. Read more |
SlashMenu | None | No equivalent | modules.slashMenu is marked deprecated in favor of modules.contextMenu, which is itself not-shipped. Do not migrate onto either; build an application-owned menu on superdoc.ui. |
AIWriter | config.modules.ai | Redesign | The built-in toolbar renders the AI writer when modules.ai is configured, and applies generated text through the Document API. |
getMarksFromSelection | superdoc.ui | Redesign | Active formatting is published as reactive controller state rather than read from a selection. Read more |
getActiveFormatting | superdoc.ui | Redesign | Active formatting is published as reactive controller state rather than read from a selection. Read more |
Extensions
| v1 | v2 | Migration | Notes |
|---|---|---|---|
Extensions | defineSuperDocExtension | Redesign | v2 extensions receive commands, anchors, and decorations. They do not receive ProseMirror state, custom schema, or mutable DOM, so extensions that defined custom nodes or marks may have no v2 equivalent. Read more |
defineNode | None | No equivalent | v2 has no custom-schema surface. Custom document nodes cannot be reproduced. Read more |
defineMark | None | No equivalent | v2 has no custom-schema surface. Custom marks cannot be reproduced. Read more |
getStarterExtensions | None | No equivalent | v2 loads its own document capabilities. There is no consumer-assembled extension list. |
getRichTextExtensions | None | No equivalent | v2 loads its own document capabilities. There is no consumer-assembled extension list. |
isNodeType | None | No equivalent | ProseMirror node predicates have no v2 equivalent. Query structure through the Document API. Read more |
assertNodeType | None | No equivalent | ProseMirror node predicates have no v2 equivalent. Query structure through the Document API. Read more |
isMarkType | None | No equivalent | ProseMirror mark predicates have no v2 equivalent. |
getSchemaIntrospection | None | No equivalent | v2 exposes no ProseMirror schema to introspect. |
registeredHandlers | None | No equivalent | Marked internal in v1 and removed in v2. |
A missing named export is only reliably caught at build time under ESM and TypeScript. In CommonJS, const { Editor } = require('superdoc') binds undefined and fails later wherever the value is used, so a CommonJS integration can appear to upgrade cleanly and then break at the call site.
Names dropped from subpaths that still exist
A subpath surviving is not the same as its exports surviving. These paths still resolve, so only the named import fails.
| v1 | v2 | Migration | What you see | Notes |
|---|---|---|---|---|
superdoc/ui type exports | superdoc/ui | Redesign | The import path still resolves, so only the named type fails. TypeScript reports the missing name; a plain JavaScript build sees nothing because these are type-only. | ContextMenuContribution, ContextMenuWhenInput, DocumentExportInput, DynamicCommandHandle, PublicToolbarItemId, SelectionAnchorRectOptions, TextSegment, ToolbarCommandHandleState, UIToolbarCommandState, ViewportContextAtInput, ViewportEntityAtInput, ViewportPositionAtInput, ViewportPositionHit, ZoomMode, ZoomViewportMetrics. v2 rebuilt superdoc/ui as a native controller rather than re-exporting v1, so these 15 type names did not carry over. Use the controller types v2 publishes from the same path. Read more |
Your application imports successfully, then fails when it runs
These compile. Some of them typecheck against the v2 configuration. They fail, or do nothing, once the editor is running.
| v1 | v2 | Migration | What you see | Notes |
|---|---|---|---|---|
editor.commands | superdoc.activeEditor.doc | Redesign | A null-property error at the point of use. The message names the command, not SuperDoc, so it does not read as a migration failure. | The v2 facade exposes commands as null rather than removing it. Optional chaining converts the throw into a silent no-op, which is harder to notice. Read more |
editor.state | superdoc.activeEditor.doc | Redesign | A null-property error, or undefined when accessed with optional chaining. | There is no ProseMirror document model in v2. Positions expressed as numeric offsets have no equivalent; use addresses and targets. Read more |
editor.view | None | No equivalent | A null-property error, or undefined when accessed with optional chaining. | v2 renders through its own engine and exposes no editor view or mutable DOM. |
superdoc.getDocumentRuntimeForDocument(documentId) | superdoc.ui | Redesign | The lookup returns null, and application code that treats the renderer as the ready UI surface throws an error such as DocumentRuntime is not available. | V2 custom UI does not need to retrieve the renderer runtime. Read the instance-owned superdoc.ui controller after onReady; it publishes reactive feature state and routes UI actions without exposing the renderer object. superdoc.ui is a getter, not an import. The instance creates it once and owns its cleanup. Read more |
documentRuntime.scrollToElement(elementId) | superdoc.ui.*.scrollTo / superdoc.scrollToElement | Redesign | The preceding runtime lookup returns null, so navigation throws or is skipped even though the document is mounted. | Route known entities through their feature handle: ui.trackChanges.scrollTo(changeId), ui.comments.scrollTo(commentId), or ui.contentControls.scrollIntoView({ id }). These methods resolve through the public host navigation surface and can reveal virtualized pages. Use superdoc.scrollToElement(elementId) when all you have is a stable block, comment, or tracked-change id. The root facade keeps the renderer object private and returns false when navigation cannot be routed. Read more |
editor.view.dispatch(tr.setSelection(...)) | superdoc.ui.selection.apply | Redesign | A null-property error on view, or undefined under optional chaining. | Setting the selection programmatically is supported. Pass a SelectionTarget to selection.apply() rather than dispatching a transaction. Read it from selection.current().selectionTarget or a query result; there is no ProseMirror position to hand it. Read more |
editor.view.posAtCoords | superdoc.ui.viewport.entityAt | Redesign | A null-property error on view, or undefined under optional chaining. | Partial. viewport.entityAt({ x, y }) resolves the public entities painted under a point (tracked changes, comments, content controls), innermost first, and returns [] over plain text. That covers hit-testing, which is what most posAtCoords callers wanted, but v2 ships no general point-to-document-position resolver, so a caller that needs an arbitrary position under the cursor has no equivalent. The legacy positional form entityAt(x, y) fails closed and returns null. Read more |
editor.view.coordsAtPos | superdoc.ui.viewport.getRect | Redesign | A null-property error on view, or undefined under optional chaining. | The opposite direction from entityAt, and a different surface. viewport.getRect({ target }) resolves painted geometry for a public target and returns { found, rects, rect, reason }. It is keyed on a target, not a numeric position, so obtain one from selection.capture() or a query result first. Read more |
manual tracked-change traversal through editor.state | superdoc.ui.trackChanges.navigateNext | Redesign | A null-property error on state, or undefined under optional chaining. | Review navigation is supported. trackChanges.navigateNext() moves through changes and trackChanges.list() reads the catalog. The catalog resolves asynchronously, so observe the slice until status is ready before consuming items. Read more |
editorExtensions | extensions | Redesign | The config typechecks and the editor mounts, but the extensions never run. SuperDoc logs a console warning naming the field. | A legacy ProseMirror concept that v2 ignores. Rebuild against extensions with defineSuperDocExtension; extensions defining custom nodes or marks may have no equivalent. Read more |
modules.collaboration | document.v2Collaboration | Redesign | The config typechecks, then SuperDoc refuses to attach the provider and reports a terminal compatibility failure. | v2 owns the provider and Y.Doc. This is a data migration, not a config change: v2 rooms use a different format and must not be pointed at an existing v1 room. Read more |
editor.commands.replaceWithFieldAnnotation (citation) | doc.citations.insert | Redesign | A null-property error on commands, or a silent no-op under optional chaining. | editor.commands.replaceWithFieldAnnotation. One v1 node type did two jobs and v2 splits them, so decide which this annotation was before porting it. This row is the citation branch: a marker backed by a source record is citations.sources.insert() followed by citations.insert({ at, sourceIds }), where sourceIds carries the canonical sourceId from the receipt rather than the source tag. Not a rename, and not a replacement either: v1 replaced its from/to range, while citations.insert resolves at, then prepends the field to the start of the containing paragraph and reports offset 0. The selected text is left where it was. Delete the range yourself if the citation is meant to stand in for it, and do not expect the citation to land at the offset you passed. If the annotation was application-owned data rather than a citation, use the metadata.attach row instead. Read more |
editor.commands.replaceWithFieldAnnotation (application annotation) | doc.metadata.attach | Redesign | A null-property error on commands, or a silent no-op under optional chaining. | editor.commands.replaceWithFieldAnnotation. The other half of the same v1 call. An annotation carrying application-owned data anchored to a range is metadata.attach({ id, namespace, target, payload }), and the entries it creates are not v1 field annotations: the ids, the payloads, and the anchoring model all differ, so treat this as remodelling rather than a rename. Read them back with metadata.list({}) and metadata.get({ id }). If the annotation was a citation with a source record, use the citations.insert row instead. Read more |
editor.state.doc.descendants (citation nodes) | doc.citations.list | Redesign | A null-property error on state, or undefined under optional chaining. | Reading citations no longer means walking a node tree. citations.list() returns the rows with their addresses and sourceIds. Item order can drive application-owned numbering, but that produces local UI labels only: it does not change the citation field result stored in the DOCX. The call is synchronous in some hosts and asynchronous in others, so shared browser and headless code should normalize with await Promise.resolve(doc.citations.list()). Read more |
editor.helpers.fieldAnnotations.getAll() | doc.metadata.list | Redesign | A null-property error on helpers, or undefined under optional chaining. | A shape change, not a rename. getAll() returned v1 field annotations; metadata.list({}) returns anchored-metadata entries whose ids, payloads, and anchoring model all differ. metadata.list is the discovery call and metadata.get({ id }) reads one payload, so pair them when you need the collection. get returns null for an entry that disappeared between the two calls. Treat legacy field annotations as something to remodel rather than something this call returns. Read more |
tr.setNodeMarkup(pos, undefined, { resolvedText }) | doc.fields.insert | Redesign | A null-property error reaching the transaction. Citations inserted through the citations API persist and export, but render with no visible text. | The citations API cannot do this: CitationInsertInput accepts only at and sourceIds, CitationUpdateInput.patch accepts only sourceIds, and displayText is on CitationInfo as a read with no writer. citations.bibliography.rebuild is not it either; it rebuilds the bibliography from current sources at a BibliographyAddress and never touches inline citation text. The one public path is a level lower: fields.insert({ at, instruction, mode: "raw", updatePolicy: "preserveCached", cachedResultText }) writes the visible result directly. Three costs come with it. You author the field instruction and keep the source records in step yourself, so you are outside the citation model rather than extending it. fields.rebuild recalculates a field and discards the preserved result. And the contract records preserveCached returning CAPABILITY_UNAVAILABLE on v1-backed sessions, so confirm it on your target runtime before depending on it. There is still no way to change the visible text of a citation the citations API already inserted. Read more |
.superdoc-text-run[data-pm-start] (citation pill styling) | ctx.visuals.highlight | Redesign | Nothing breaks, which is the problem. The painter still stamps data-pm-start on runs, so the selector keeps matching and the code keeps mutating renderer-owned DOM. | This one does not fail on upgrade. layout-engine/dom-contract stamps data-pm-start and data-pm-end alongside the editor-neutral attributes specifically so v1 consumers keep working, and .superdoc-text-run is still the painted class. What you lose is the guarantee: the DOM shape is renderer-owned and carries no stability contract, so this is worth migrating before a paint change breaks it silently rather than after. The supported replacement is a painted class through ctx.visuals.highlight(id, { className }), or ctx.decorations.register underneath it. A CitationAddress is not itself a visual target, but converting one is mechanical rather than blocked, because both sides use the same coordinate system. CitationAddress.anchor is an InlineAnchor of { blockId, offset } positions, and Position.offset is a 0-based offset into the block's flattened text, which is what SuperDocTextAddress.range expects: { kind: "text", blockId: anchor.start.blockId, range: { start: anchor.start.offset, end: anchor.end.offset } }. The real limit is the class. className accepts paint properties only: background, color, text-decoration, outline, and box-shadow, explicitly not padding, margin, display, or position. Recolouring citation text works; a padded pill shape does not. Decorations are render-only and never reach the DOCX. |
superdoc.on('fieldAnnotationClicked') | None | No equivalent | The subscription is accepted and the handler never fires. | v2 emits no per-entity click events, and nothing recovers the identity this one carried. superdoc.ui.viewport.entityAt is the supported point lookup, but it collects only tracked changes, comments, and content controls, so it cannot return a field annotation and is not a replacement for this event. Whether hit-testing helps at all depends on what the annotation became: content controls are returned, citations and anchored metadata are not. Decide the v2 model for the annotation first, then pick the lookup that model supports. |
editor.state.selection | superdoc.ui.selection.current | Redesign | A null-property error on state, or undefined under optional chaining. | A v1 Selection was one live object used for both reading and preserving, and v2 splits those. To read the current selection use selection.current() or the observed snapshot; both describe a collapsed caret. To preserve a selection across something that moves focus, use capture() and then restore(). Do not reach for capture() as the general read: it returns null whenever the selection is empty, which includes a collapsed caret, so caret-dependent code migrated onto it loses the selection it was trying to keep. Either way the payload is document addresses rather than numeric positions. Read more |
document.getSelection().getRangeAt(0).getBoundingClientRect() | superdoc.ui.selection.getAnchorRect | Redesign | The browser selection reflects rendered DOM rather than document state, so the rectangle is wrong or the call throws on an empty range. | getAnchorRect() resolves one rectangle from the document selection against the painted layout, and accepts 'start', 'end', or 'center'. v1's 'union' placement has no v2 value: use selection.getRects() when a bubble needs the full multi-line union. |
document.querySelector('[data-comment-id="..."]') | superdoc.ui.viewport.getRect | Redesign | Depends on the sidebar, which is the trap. With the built-in comments UI enabled the selector matches a sidebar card and positions against the wrong element; with it disabled the same selector matches nothing. | The painter stamps no singular data-comment-id on document content, but FloatingComments.vue binds one on each sidebar placeholder, so an integration running the built-in comments UI gets a match that is a panel card rather than the painted anchor. Code that scrolls or positions from it silently targets the sidebar. Turn the sidebar off and the same selector returns null, so the failure changes shape with configuration rather than being absent. There is a second near miss: the painter stamps data-comment-ids, plural and comma-separated, on .superdoc-comment-highlight, and retargeting the selector there puts you back on renderer-owned DOM with no stability contract. Read the comment through ui.comments.getById() and resolve its target with viewport.getRect({ target }) instead. Treat a null from getById() as "not loaded yet" rather than "no such comment": it checks the loaded snapshot, then falls back to the Document API, and returns null when that fallback is a pending promise, which is the normal browser case. It does not prime the read either, unlike contentControls.getById(), and comments.list() returns [] on the same path. Gate on the observed comments slice, and gate on listStatus rather than status: status folds in the live selection read, so an unrelated selection change holds it at pending while the list is ready. Do not cache the rectangle as identity: geometry changes on scroll, zoom, resize, and pagination, and viewport.observe() is the signal to measure again. Read more |
document.querySelector('[data-track-change-id="..."]').scrollIntoView() | superdoc.ui.trackChanges.scrollTo | Redesign | Usually a match, and a TypeError when not. The call as written dereferences the result, so a change that is not painted takes down whatever runs it, and the same code works in a short document and throws in a long one. | This survives the upgrade, which is why it is worth migrating deliberately. The v2 painter still stamps data-track-change-id on painted runs, so the selector keeps resolving and nothing announces that it has become unreliable. It misses in two different ways. A change on a page that is not currently painted has no element at all, because querySelector sees only mounted DOM and virtualization leaves the rest of the document unrendered. And where several changes affect one marker, only one id lands in the singular attribute: the rest are in data-track-change-ids, comma-separated, so a secondary overlapping change is unreachable by the selector even while it is on screen. Both misses reach .scrollIntoView() on null and throw, unless the call is guarded. The attribute is renderer-owned and carries no stability contract either. trackChanges.scrollTo(changeId) takes the id rather than a selector, mounts the target page when it has to, resolves overlapping changes through the same catalog the review UI uses, and reports whether it succeeded. Read more |
new MutationObserver(...) on the editor element | superdoc.ui.viewport.observe | Redesign | The observer attaches and fires on repaints that do not correspond to document changes, or never fires for layout changes that produce no mutations. | Watching the rendered DOM was always a proxy for the question consumers were asking. viewport.observe() fires when painted geometry is invalidated, including scroll, zoom, resize, pagination, and virtualized page mounts, which a subtree MutationObserver either misses or floods on. Read more |
element.scrollIntoView() on a content-control element | superdoc.ui.contentControls.scrollIntoView | Redesign | There is no element to hold: the query that produced it matches nothing, so the call throws or is skipped. | Address the control by id rather than by element: contentControls.scrollIntoView({ id, block }) resolves it through the host, including on pages that are not currently painted. A DOM-held reference cannot survive virtualization. |
editor.comments.filter((c) => c.isTrackedChange) | superdoc.ui.trackChanges.list | Redesign | A null-property error on comments. Once ported, the more damaging failure is quiet: a document full of tracked changes reports none. | v2 separates tracked changes from comments instead of tagging one list. The trap is readiness, not the split: the catalog is an async read, so a synchronous list() on a controller that has not settled returns an empty snapshot rather than an error. Observe the slice and consume items once status is ready. total and authors come from the same slice and carry the same caveat. Read more |
editor.presentationEditor | superdoc.activeEditor.pageMetrics | Redesign | A null-property error on presentationEditor, or undefined under optional chaining. | presentationEditor.getPages, presentationEditor.onLayoutUpdated, presentationEditor.element. Page metrics survive as a capability. pageMetrics exposes getSnapshot, subscribe, setZoom, scrollToPage, revealBodyTarget, and pageIndexForBodyTarget, and ui.viewport.getHost() replaces reading presentationEditor.element. The facade types pageMetrics as unknown, so consuming it means declaring the shape yourself and re-verifying it on upgrade; it is not part of the typed public surface the rest of this catalog points at. |
.superdoc-page[data-page-index] (page element lookup) | superdoc.activeEditor.pageMetrics | Redesign | Usually nothing, which hides the two cases that matter. The painter still stamps data-page-index on .superdoc-page, so the selector keeps matching for mounted pages and returns null for virtualized ones. | The DOM lookup survives the upgrade rather than breaking, so this migrates on correctness rather than on a build error. Page windowing means only mounted pages have elements, and a page outside the window returns null from the selector while still having metrics. Read the page from the metrics snapshot instead. The same untyped-surface caveat applies as for presentationEditor, and the painted DOM carries no stability contract even where it currently answers. |
onTransaction | defineSuperDocExtension + ctx.onMutation | Redesign | The config typechecks and the editor mounts, and the callback never fires. | v2 has no transactions to hand back. Declare an extension and subscribe through its activation context: ctx.onMutation({ origin, sourceComplete, affects }, handler). The filter is the point of the redesign, because a v1 handler had to inspect every transaction and decide whether it mattered. |
editor.chain().insertPageBreak().run() | doc.create.paragraph + doc.format.paragraph.setFlowOptions | Redesign | A null-property error on chain, or undefined under optional chaining. | Not a behavioral equivalent, and the difference decides the port. v1 broke at the caret. v2 creates a paragraph at an address you supply and sets pageBreakBefore on it, which starts that paragraph on a new page rather than inserting a standalone break character. A mid-paragraph caret has no address of its own, so code that broke mid-paragraph must split the paragraph first or accept a block boundary. Await the creation: its returned address is the target of the formatting call. Read more |
doc.extract({}) / doc.getMarkdown({}) / doc.selection.current({}) read synchronously | the same operations, awaited | Redesign | The call returns a Promise where the code expected a value, so reads look empty and downstream property access yields undefined. | The v2 browser facade can route reads and mutations through a worker even when the underlying operation is synchronous in headless Node, so the same code is sync in one host and async in the other. Await browser calls, or normalize shared browser and headless code with await Promise.resolve(call), and keep author overrides or other scoped state active until an awaited mutation settles. Read more |
ui.createScope().register(...) | superdoc.ui.commands.register | Redesign | A null-property error on createScope, or undefined under optional chaining. | Register on the instance-owned controller: superdoc.ui is created once on first read and reused, and its type omits destroy() because the instance owns teardown. The registration result is callable and also exposes unregister(); keep either and call it on unmount. Command callbacks receive the public doc, ui, selection, and mode rather than ProseMirror command state, and the selection should be read from context.selection, which is synchronous and always present, not through the deep-partial context.doc. Read more |
editor.on('selectionUpdate') | superdoc.ui.selection.subscribe | Redesign | The subscription is accepted and the handler never fires. | subscribe fires immediately and passes { snapshot }; observe is the value-shaped alias. Both return an unsubscribe function, where v1 required a matching off. The payload is a public selection slice with document addresses, not a ProseMirror selection, so handlers that read positions have to be rewritten rather than rebound. |
trackedChange.authorColor | superdoc.ui.trackChanges.list | Redesign | Present on some rows and absent on others. Rows without it render undefined as a color, and nothing errors. | Conditional rather than removed. The row projection passes the v2 tracked-changes facade fields through, so list() returns authorColor for rows whose host supplies one. It is not declared on TrackChangeInfo, so it is not part of the typed public surface and reading it means asserting the shape yourself. trackChanges.authors is flattened to readonly string[] and drops the per-author color the facade returns, so it is not the fallback either. Keep an application-owned default rather than assuming the field is there, and do not mutate the readonly slice arrays to add one. Read more |
editor.view.dom.addEventListener('paste', ...) | the element your application passed as the SuperDoc host | Redesign | A null-property error on view, or undefined under optional chaining. | The capability survives as ordinary DOM work, but the element does not: v2 owns and re-paints its internal DOM, so attach the listener to the stable host element you passed to SuperDoc and remove it on unmount. A bubbling listener is enough when validation only needs preventDefault(). This guards browser input only; programmatic Document API mutations emit no DOM paste events. |
The editor internals are the harder half. v2 exposes commands, state, and view as null rather than removing them, so reading a property off one raises a generic null-property error that names your command and not SuperDoc. Optional chaining is worse: it turns the same mistake into a silent no-op that never errors at all.
Exact error wording varies by browser, bundler, and minifier. Treat the "What you see" column as symptoms to recognize, not as strings to match on.
Before you start
Collaboration is not a configuration change. v2 rooms use a different document format, v2 owns the provider and Y.Doc, and a v2 editor must never be pointed at an existing v1 room. If your application uses collaboration, plan that migration separately before upgrading anything else.
Continue with the v1 migration guide for the full upgrade path, or the Document API mental model to understand what replaces direct editor access.