Guides

Migrating from #tiptap-editor

Move a host from the removed #tiptap-editor Nuxt module to explicit imports from @skaile/forge-editor entries.

The former Nuxt module modules/tiptap-editor (alias #tiptap-editor, auto-imported components, composables and utils, one server directory) is gone. Nothing is auto-imported and no alias remains: import from the entries below.

  1. nuxt.config.ts: replace './modules/tiptap-editor' with '@skaile/forge-editor/nuxt' (after '@nuxt/ui'). The tiptapEditor options keep their shape and defaults. Add "@skaile/forge-editor": "workspace:*" to the host's dependencies.
  2. Replace auto-imported components and composables by explicit imports:
Before (auto-import or #tiptap-editor)After
DocumentEditorAdvancedEditor from @skaile/forge-editor/vue/advanced (same props and events; plus settings-ui, see Presets and features); on React @skaile/forge-editor/react/advanced
EditorToolbar, EditorSettingsModal, useEditorSettings, useEditorUi, useEditorContext@skaile/forge-editor/vue (the toolbar and the panel take :editor)
AiSidebar@skaile/forge-editor/ai/vue/chat (needs the optional peer @skaile/forge-chat; /ai/react on React)
AiReviewBar, AiSelectionMenu, ModelQuickSwitch, useAiAssistant, useDocumentChat, useModelProfiles@skaile/forge-editor/ai/vue (/ai/react on React; the framework-free models modelProfiles, createAiAssistant, createDocumentChat in @skaile/forge-editor/ai)
useFileImport, AudioRecorderModal, useAudioRecorder, pickFiles@skaile/forge-editor/media/vue (/media/react on React); pickFiles and the framework-free models in @skaile/forge-editor/media
createTranscriptCleanup, hasTranscriptSegments@skaile/forge-editor/integrations/transcript-ai
The Block type API (the module's former Composite* names; today defaultBlockTypes(t), BlockType, acceptsChild), AcceptedChildren, headingsMarkdown, compositeOutline, moveBlockInto, moveIntoVerdict, markdown fence helpers, CompositeTable, CompositeInspector, useCompositeRows, setComposite* writers@skaile/forge-editor/composites (framework-free), /composites/vue or /composites/react for the components and useCompositeRows (schema subset: /composites/schema)
headingOutline, revealBlockInEditor (framework-free), EditorOutline (/outline/vue, /outline/react)@skaile/forge-editor/outline
EMPTY_EDITOR_CONTENT, ABILITY, Ability, findBlockById, block ID helpers@skaile/forge-editor/core/schema
fetchErrorMessage, downloadText, abilities helpers (abilitiesAtSelection, hasAbility)@skaile/forge-editor/core
DocumentTree, useTreeView, TreeFolder, DocumentSummary (were host files and host shared types)DocumentTree, useTreeView from @skaile/forge-editor/tree/vue (or /tree/react); DocumentStore, TreeFolder, DocumentSummary from @skaile/forge-editor/tree (the host's DocumentStore implementation stays in the app; the host's ref-based store becomes stores with storeFromRef from @skaile/forge-editor/vue)
setEditorAdapter, forgetCollabDocument, liveContent, EditorAdapter (Nitro, previously auto-imported)provideEditorServer({ ports }), useEditorServer().forgetDocument, useEditorServer().liveContent from @skaile/forge-editor/server/nitro, EditorPorts from @skaile/forge-editor/server
Server schema / migration imports of the old shared directory@skaile/forge-editor/schema/advanced, /composites/schema, /media/schema, …
  1. Collaboration, block UI and the integrations are now features. AdvancedEditor includes them; a custom preset adds collabFeature(), blockUiFeature() and the integrations explicitly. Features report user-facing messages through the surface's Notifier (ctx.notify, per-feature entries), so host code no longer hands toast objects to feature functions.
  2. Search the host for #tiptap-editor, modules/tiptap-editor and for symbols that used to resolve without an import (useEditorSettings, useFileImport, ABILITY, fileNameError's neighbors…); nuxt typecheck reports every unresolved one.
  3. Styles: the Nuxt module adds every feature stylesheet; a plain-Vue app imports core.css plus the feature CSS it uses (README).

Stored data needs no migration: document JSON, Yjs state, tiptap-editor:settings, tiptap-editor:model-profiles and tip-tree:node-filters keep their keys and shapes. Legacy section nodes are still migrated on load (currentContent on the server, the composites Feature's migrateContent hook in the editor).

Block vocabulary

The library, the reference app and the docs name things in the block vocabulary (glossary: Typed block, Block type, Property definition, Model role, …). The former names are removed; no alias remains. Switch a host over like this:

Former nameNew nameWhere
CompositeKind, CompositeKindAi, CompositeMetaFieldBlockType, BlockTypeAi, PropertyDefinition@skaile/forge-editor/composites, /composites/schema; PropertyDefinition from @skaile/forge-editor/core
defaultCompositeKinds, fallbackCompositeKinds, compositeKind, kindAcceptsdefaultBlockTypes(t), fallbackBlockTypes, blockTypeOf, acceptsChildthe same entries
compositeKindsOf(editor)blockTypesOf(editor)the same entries
CompositeKindCount, CompositeKindOption, ChatCompositeKindBlockTypeCount, BlockTypeOption, ChatBlockTypethe same entries; the chat type in @skaile/forge-editor/ai
command setCompositeKind(kind, pos?)changeBlockType(blockTypeId, pos?)editor commands of the Typed block (not setBlockType: ProseMirror uses that name for node-type changes)
compositeMetaWith, setCompositeMetaFieldmetaWithProperty, setBlockProperty@skaile/forge-editor/composites
renditionsOfderivedChildrenOf@skaile/forge-editor/core
option and prop kinds or compositeKindsblockTypes (compositeFeature({ blockTypes }), composeEditor({ blockTypes }), AdvancedEditor, ComposedSurface)the same entries
skeleton in the ai brief of a Block type, and the AI wire field block.skeletoncontentTemplateBlockTypeAi; client and server of the AI endpoints ship in the same package
ModelSlot, MODEL_SLOTS, MODEL_SLOT_KEYSModelRole, MODEL_ROLES, MODEL_ROLE_KEYS@skaile/forge-editor/settings (the stored key ai.slots is now ai.roles; saved settings migrate on load)
PresetConfigModelPresetConfig@skaile/forge-editor/config, /server
Identity, DocumentStore of the server portsAccessControl, DocumentContentStore@skaile/forge-editor/server (the EditorPorts members identity and documents keep their names; the Document tree's own DocumentStore in @skaile/forge-editor/tree keeps its name)
the tree command string of the folder blockcreateFolderBlockTreeCommand of @skaile/forge-editor/tree; a host executor that switches on the command type must use the new string
aiKindsOf, derivableKindsOf, AiContextKindaiBlockTypesOf, derivableBlockTypesOf, AiContextBlockType@skaile/forge-editor/composites, /integrations/composite-ai, /core
AiContext.kinds, ChatBlockModel.kinds, the AI chat request field kindsblockTypes@skaile/forge-editor/core, /ai; client and server of the AI endpoints ship in the same package
ConvertTarget { type: 'typedBlock', kind }{ type: 'typedBlock', blockType } (the target id typedBlock:<id> is unchanged)@skaile/forge-editor/core
BlockProperties.fieldsBlockProperties.definitions@skaile/forge-editor/composites
listsBlockTypeCapability(type, id), listsCapability(ctx, node, id)listsCapability(type, id) (pass ctx.blockTypeOf(node))@skaile/forge-editor/core
blockTypeConfigOf from /compositesblockTypeConfigOf(type, id)@skaile/forge-editor/core; configOf and capabilityConfigOf now return undefined (was null) when absent
BlockTypeSpecBlockType@skaile/forge-editor/composites
derivationOf, derivationState, DERIVATION_KEY, Derivation, DerivationMethod, DerivationSource, PropertyDefinition from /composites or /composites/schemathe same names@skaile/forge-editor/core only
CSS --sk-kind-tone, --sk-kind-tone-mix--sk-block-type-tone, --sk-block-type-tone-mixhost stylesheets that set or read the tone

Behaviour that changed with the rename:

  • Accepted children. The built-in Folder and Document Block types declare accepts: 'all' themselves; the id-keyed default is gone. A Block type without accepts, and a type ID that a list does not define, accepts no nested Typed blocks (acceptsChild). A host list that defines its own folder or document Block type must set accepts on it.
  • Block UI. The hover bar, the toolbar Block actions and the drag handle address the outermost block, so a caret in a list item or a quote paragraph acts on the whole list or quote (Block UI).
  • Labels. English: Typed block, Block type, Readable rendition, Theme with Default appearance, Document facts, Structure mode and Flat mode. German: Block and Blocktyp; the two German labels that still use the legacy words for basket and readable rendition stay. Message keys keep their IDs, so messages overrides still apply, but check the wording of an override against the new labels.

Block types as data

A BlockType (@skaile/forge-editor/composites) is now an id, its presentation (label, icon, titlePlaceholder, descriptionPlaceholder, chrome) and a list capabilities: { id, config? }[]. The former switches move into it (Typed blocks):

Former fieldNow
fields: PropertyDefinition[]{ id: 'properties', config: { definitions } }
source: ('image' | 'audio' | 'video' | 'file')[]{ id: 'source', config: { media } }
accepts: string[] | 'all'{ id: 'container', config: { accepts } }
ai: BlockTypeAi{ id: 'ai-brief', config: ai }
(every type had a Basket menu){ id: 'basket' }; a type without it has no Basket menu
a bibliography property implied the BibTeX export{ id: 'bibliography' }

A host list that spreads defaultBlockTypes(t) and adds its own types must give them the capabilities they need. An unknown capability id or an invalid config throws when the editor is created. meta keys and the ::: <Block type id> Markdown fences keep their names; the node and its type attribute are renamed (below).

Persisted names

Stored documents and settings use the vocabulary of the glossary. Everything saved before loads through a migration that reads the old names and never writes them back; hosts do nothing for stored data.

OldNewWhere
node compositetypedBlockdocument JSON, Yjs state, .cdoc packages, clipboard and drag payloads
compositeTitle, compositeDescription, compositeTagstypedBlockTitle, typedBlockDescription, typedBlockTagssame
attribute kind on the Typed block and on opaqueFenceblockTypesame; block IDs (attrs.id) and meta are untouched
HTML section[data-type="composite"][data-kind]section[data-type="typedBlock"][data-block-type]clipboard HTML: both shapes parse, only the new one is written
BlockTransfer.kind (drag payload on application/x-tip-block)BlockTransfer.blockTypean old payload is read, its json migrated

Markdown needs no migration: the fence is ::: <Block type id> #id {meta} before and after, so old Markdown parses to Typed blocks and the export is byte-identical.

The server migrates Yjs state when a document loads (onLoadDocument, before any client syncs): it reads the schema-less JSON of the stored state, applies the same JSON migration and writes the result back with the block IDs and content unchanged, then persists the migrated state. A second load finds nothing to migrate. Client JSON goes through the rename in defineEditor, which runs ahead of any contributed migrateContent (the Typed blocks feature contributes the legacy-node migration), so a host hook cannot drop it.

Settings version 2 renamed four keys (table below). Saved settings (server store, localStorage) migrate on load and are rewritten once. The host config key change needs a code change (no migration).

OldNewWhere
ai.slotsai.rolessettings; host config key (code change, no migration)
wire key slots of GET /api/ai/modelsrolesHTTP response
blocks.defaultKindblocks.defaultBlockTypesettings
blocks.kindIconblocks.blockTypeIconsettings
blocks.structure value 'composite''structured'settings
KIND_ICON_VISIBILITIES, KindIconVisibilityBLOCK_TYPE_ICON_VISIBILITIES, BlockTypeIconVisibility@skaile/forge-editor/settings (code names, nothing stored)
Copyright © 2026