AI agents

Integrate the editor

Agent playbook for adding @skaile/forge-editor to an app: choose the surface, then the entry to import, then follow the matching setup page.

Choose the surface

Decide first which host the editor lands in. Each row links to the page that holds the ordered setup steps and the code; do not retype them from memory.

HostSurface and entrySetup
Nuxt app (editor plus backend)AdvancedEditor from @skaile/forge-editor/vue/advanced, module @skaile/forge-editor/nuxt, EditorPorts in a Nitro pluginQuick start: Nuxt
Plain Vue 3 app (Vite), no backendSimpleEditor from @skaile/forge-editor/vue/simple, createEditor()Quick start: Vue
React 19 appSimpleEditor from @skaile/forge-editor/react/simple inside EditorContextProvider; AdvancedEditor from @skaile/forge-editor/react/advancedQuick start: React, Advanced editor in React
Framework-free (headless, server, validator)/core, the <x> entries without /vue or /react, schema/*, /serverPublic exports

The Nuxt path in order: install the package and its peers (Installation); register the module after @nuxt/ui; register EditorPorts in a Nitro plugin (EditorPorts guide); mount the editor inside <UApp> with the toolbar as a sibling that receives the editor from ready. The Nuxt module installs the client plugin with createEditor; plain Vue and React hosts create the context themselves.

AdvancedEditor pulls in every feature. For a smaller bundle use SimpleEditor or compose a list yourself.

Choose a preset or features

Which preset or feature list fits the goal, and what each feature needs from the backend: Composing an editor (decision table) and Presets and features (every feature with requires and backend). Own features: Add a block or feature.

Which entry to import

Every entry is an explicit subpath of package.json#exports; a path that is not listed there does not resolve. Full inventory with every exported name: Public exports.

You needImportSide
A ready simple editor componentSimpleEditor from @skaile/forge-editor/vue/simplebrowser
A ready advanced editor componentAdvancedEditor from @skaile/forge-editor/vue/advancedbrowser
A preset to pass to EditorSurface, EditorToolbar, EditorSettingsPanelsimpleEditor from .../presets/compose (framework-free, no ⌘K palette), advancedEditor / composeEditor from .../vue/advanced or .../react/advanced (the framework-free assembly behind them is .../presets/compose: composeEditor(ui, options), CONTENT, INTERACTION, AI)browser
Vue chrome: createEditor (mandatory Vue app context), EditorSurface, EditorToolbar, EditorSettingsPanel, EditorSettingsModal, EditorCommandPalette (⌘K palette, the core-ui overlay), SettingsSection, useEditorSettings, useEditorUi@skaile/forge-editor/vuebrowser (Vue)
React chrome: EditorContextProvider (mandatory React context from createEditorContext()), EditorSurface, EditorToolbar, EditorSettingsPanel, EditorSettingsModal, EditorCommandPalette (⌘K palette, the core-ui overlay), SettingsSection, useEditorSettings, useEditorUi, Toaster, reactNotifier@skaile/forge-editor/reactbrowser (React)
A ready simple editor component for ReactSimpleEditor from @skaile/forge-editor/react/simplebrowser (React)
A ready advanced editor component for ReactAdvancedEditor, advancedEditor from @skaile/forge-editor/react/advanced (needs EditorContextProvider and Toaster)browser (React)
shadcn token mapping for the feature CSS (React hosts)@skaile/forge-editor/react/styles/ui-vars.css; for the advanced editor @skaile/forge-editor/react/styles/advanced.css (token mapping, core.css, every feature sheet, themes.css and the theme bridge in one import)browser (React)
Framework-free core: defineEditor, coreContentFeature, coreUiFeature, createEditorContext, command, commandState, parseBlocks, createStore, Notifier, extension-point types (MenuItem, EditorFeature<C>)@skaile/forge-editor/corebrowser (no UI framework)
One feature<x>FeatureCore(ui), models and types from .../<x>; the Vue binding <x>Feature() and components from .../<x>/vue; the React binding and components from .../<x>/react; <x> is media, mentions (framework-free only), transcript, authors, comments, composites, ai, collab, block-ui, themesbrowser
Markdown string as the documentsplitFrontmatter, joinFrontmatter, markdownToContent(editor, body, { policy }) → { content, diagnostics } (raw HTML, unsafe URLs, unknown fences reported; OpaqueFence keeps unknown ::: kind fences byte for byte), contentToMarkdown(editor), MarkdownClipboard (paste/copy as Markdown), createSourceMode({ getEditor }) (state, enter, edit, commit → false and editor untouched on a fatal diagnostic, cancel) from .../markdown; MarkdownSurface (v-model string, YAML frontmatter kept verbatim, props policy and sourceMode) from .../markdown/vue (React: value/onChange/onDiagnostics, prop policy, no source view, from .../markdown/react); GFM tables and task lists: tablesFeature() from .../tables (schema .../tables/schema, CSS .../styles/tables.css)browser (.../markdown, .../tables also headless)
Edit a code, YAML, JSON or text fileCodeSurface from .../code/vue (v-model, editable, language, wordWrap, emits save, ready) or .../code/react (value, onChange, onSave, onReady); framework-free createCodeEditor, setReadOnly, setWordWrap, setLanguage, loadLanguage, setDocument, formatJsonInEditor, kindOf(path, mime?), languageOf(path) from .../code. Language packs are yours: registerLanguage('yaml', async () => (await import('@codemirror/lang-yaml')).yaml()) once at startup, id from languageOf(path). Theme = --sk-* tokens (.../styles/tokens.css). Needs @codemirror/{state,view,language,commands,search} and @lezer/highlight installed (optional peers); no ProseMirror/TipTap in the bundlebrowser
One surface for Markdown and code filesDocumentSurface from .../document/vue (path, v-model, markdown = props of MarkdownSurface incl. preset, binary slot) or .../document/react (path, value, onChange, markdown, binary(path)); chooses by kindOf(path, mime); remount (:key/key = path) when the path changes. Reaches ProseMirror and CodeMirror bothbrowser
An integration (framework-free, works on both surfaces)compositeMediaFeature, compositeAiFeature, transcriptAiFeature from .../integrations/composite-media, /composite-ai, /transcript-aibrowser
Heading outline, Document treeEditorOutline from .../outline/vue or .../outline/react (headingOutline, revealBlockInEditor from .../outline); DocumentTree, useTreeView from .../tree/vue or .../tree/react; DocumentStore, createTreeView from .../treebrowser
Settings type, defaults, validationEditorSettings, EDITOR_SETTINGS_DEFAULTS, parseEditorSettings, editorSettingsSchema from .../settingsanywhere
ProseMirror schema for a server, validator or migrationsimpleSchemaExtensions from .../schema/simple; advancedSchemaExtensions from .../schema/advanced; per-feature .../<name>/schema; base coreSchemaExtensions, ROOT_ABILITIES, BlockId helpers from .../core/schemaframework-free, Node-safe
Nitro side: register the ports, forget a deleted documentprovideEditorServer, useEditorServer (.forgetDocument, .liveContent, .enforceRateLimit, .resolveModel) from .../server/nitro, EditorPorts from .../serverNitro only
Server without Nitro: collab server, AI models, rate limiter, route handlers, websocket listenercreateCollabServer, createAiModels, createRateLimiter from .../server; chatPost, usersGet, … and their body schemas from .../server/handlers; listenCollab from .../server/nodeany Node process
Store documents as JSON filescreateJsonAdapter({ dir, authenticate?, authorize?, users? }) (an EditorPorts bundle) from @skaile/forge-editor/adapters/jsonNode
Store documents in SQLite via drizzle (optional peer drizzle-orm)bootstrapEditorTables(db), createDrizzleAdapter({ db, tables: editorTables() }) from @skaile/forge-editor/adapters/drizzleNode
Edit a host's Markdown filescreateForgeAdapter({ content, state, schema, authenticate, … }) from @skaile/forge-editor/adapters/forgeNode
Mount the editor backend on Nitro (no Nuxt module)installEditorServer(nitroApp, { ports, collab: {} | false, ai?, rateLimit? }) from @skaile/forge-editor/server/nitro (comments: ports.comments), called from one Nitro plugin; needs experimental: { websocket: true } for collab; peer h3Nitro only
Bind an editor surface to a documentuseEditorDocument(id, { backend?, collaboration?: boolean | { token? }, user?, canComment? }) → Vue: { modelValue, ready, dirty, save, reload, error, collaboration, comments, editor, bind, dispose } refs from @skaile/forge-editor/adapters/vue; React: the same as plain values plus onChange from @skaile/forge-editor/adapters/react; backend defaults to the editor context's (needs createHttpBackend() for collaboration and comments)browser
Comment threads that survive editscaptureAnchor/resolveAnchor/mapAnchor from .../anchors; createJsonCommentStore({ file }) from .../comments/json; createDrizzleCommentStore({ db, tables: commentTables() }) from .../comments/drizzle; handleCommentsRequest(store, { method, documentId, body }) from .../comments/server; the browser side is backend.comments(documentId, …) of createHttpBackend() from .../adapters/httpanchors: browser (no UI framework); the others: Node
Nuxt module (config key tiptapEditor)@skaile/forge-editor/nuxt in modulesbuild
One config for module, server and clientdefineEditorConfig(input), serverConfig(config), publicConfig(config) from .../config (defaults once; installEditorServer(nitroApp, { ports, ...serverConfig(config) }), createEditor({ ...publicConfig(config) }); the Nuxt module options are its input)anywhere
CSS.../styles/core.css (required) plus /media.css, /mentions.css, /tables.css, /authors.css, /comments.css, /transcript.css, /composites.css, /ai.css, /collab.css, /block-ui.css, /themes.css for the features you usebrowser

Rules that follow from this table:

  • Import values from the subpath that owns them. The root entry @skaile/forge-editor exports types only (export type), so import { createEditor } from '@skaile/forge-editor' does not compile; use @skaile/forge-editor/vue (Vue) or @skaile/forge-editor/react (React). /core contains no vue, @tiptap/vue-3, @nuxt/ui or React module and no longer exports createEditor, EditorSurface, EditorToolbar, EditorSettingsPanel, EditorSettingsModal, SettingsSection, useEditorSettings, useEditorUi, useEditorContext: those come from /vue or /react. Feature entries follow the same rule: /<x> is framework-free, the components and the <x>Feature() binding live in /<x>/vue and /<x>/react. A React preset is defineEditor({ features: [coreContentFeature(), coreUiFeature(), mentionsFeature(), ...] }) with factories from /<x>/react; features with Vue components are not assignable to it.
  • Never import a browser entry from server code, and never import /server or /nuxt from browser code. Servers use the framework-free schema/protocol/types entries (/ai/protocol, /integrations/transcript-ai/protocol, /tree/types, /settings).

After the change

Check the invariants and run the verification commands.

Copyright © 2026