Integrate the editor
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.
| Host | Surface and entry | Setup |
|---|---|---|
| Nuxt app (editor plus backend) | AdvancedEditor from @skaile/forge-editor/vue/advanced, module @skaile/forge-editor/nuxt, EditorPorts in a Nitro plugin | Quick start: Nuxt |
| Plain Vue 3 app (Vite), no backend | SimpleEditor from @skaile/forge-editor/vue/simple, createEditor() | Quick start: Vue |
| React 19 app | SimpleEditor from @skaile/forge-editor/react/simple inside EditorContextProvider; AdvancedEditor from @skaile/forge-editor/react/advanced | Quick start: React, Advanced editor in React |
| Framework-free (headless, server, validator) | /core, the <x> entries without /vue or /react, schema/*, /server | Public 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 need | Import | Side |
|---|---|---|
| A ready simple editor component | SimpleEditor from @skaile/forge-editor/vue/simple | browser |
| A ready advanced editor component | AdvancedEditor from @skaile/forge-editor/vue/advanced | browser |
A preset to pass to EditorSurface, EditorToolbar, EditorSettingsPanel | simpleEditor 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/vue | browser (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/react | browser (React) |
| A ready simple editor component for React | SimpleEditor from @skaile/forge-editor/react/simple | browser (React) |
| A ready advanced editor component for React | AdvancedEditor, 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/core | browser (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, themes | browser |
| Markdown string as the document | splitFrontmatter, 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 file | CodeSurface 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 bundle | browser |
| One surface for Markdown and code files | DocumentSurface 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 both | browser |
| An integration (framework-free, works on both surfaces) | compositeMediaFeature, compositeAiFeature, transcriptAiFeature from .../integrations/composite-media, /composite-ai, /transcript-ai | browser |
| Heading outline, Document tree | EditorOutline from .../outline/vue or .../outline/react (headingOutline, revealBlockInEditor from .../outline); DocumentTree, useTreeView from .../tree/vue or .../tree/react; DocumentStore, createTreeView from .../tree | browser |
| Settings type, defaults, validation | EditorSettings, EDITOR_SETTINGS_DEFAULTS, parseEditorSettings, editorSettingsSchema from .../settings | anywhere |
| ProseMirror schema for a server, validator or migration | simpleSchemaExtensions from .../schema/simple; advancedSchemaExtensions from .../schema/advanced; per-feature .../<name>/schema; base coreSchemaExtensions, ROOT_ABILITIES, BlockId helpers from .../core/schema | framework-free, Node-safe |
| Nitro side: register the ports, forget a deleted document | provideEditorServer, useEditorServer (.forgetDocument, .liveContent, .enforceRateLimit, .resolveModel) from .../server/nitro, EditorPorts from .../server | Nitro only |
| Server without Nitro: collab server, AI models, rate limiter, route handlers, websocket listener | createCollabServer, createAiModels, createRateLimiter from .../server; chatPost, usersGet, … and their body schemas from .../server/handlers; listenCollab from .../server/node | any Node process |
| Store documents as JSON files | createJsonAdapter({ dir, authenticate?, authorize?, users? }) (an EditorPorts bundle) from @skaile/forge-editor/adapters/json | Node |
Store documents in SQLite via drizzle (optional peer drizzle-orm) | bootstrapEditorTables(db), createDrizzleAdapter({ db, tables: editorTables() }) from @skaile/forge-editor/adapters/drizzle | Node |
| Edit a host's Markdown files | createForgeAdapter({ content, state, schema, authenticate, … }) from @skaile/forge-editor/adapters/forge | Node |
| 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 h3 | Nitro only |
| Bind an editor surface to a document | useEditorDocument(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 edits | captureAnchor/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/http | anchors: browser (no UI framework); the others: Node |
Nuxt module (config key tiptapEditor) | @skaile/forge-editor/nuxt in modules | build |
| One config for module, server and client | defineEditorConfig(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 use | browser |
Rules that follow from this table:
- Import values from the subpath that owns them. The root entry
@skaile/forge-editorexports types only (export type), soimport { createEditor } from '@skaile/forge-editor'does not compile; use@skaile/forge-editor/vue(Vue) or@skaile/forge-editor/react(React)./corecontains novue,@tiptap/vue-3,@nuxt/uior React module and no longer exportscreateEditor,EditorSurface,EditorToolbar,EditorSettingsPanel,EditorSettingsModal,SettingsSection,useEditorSettings,useEditorUi,useEditorContext: those come from/vueor/react. Feature entries follow the same rule:/<x>is framework-free, the components and the<x>Feature()binding live in/<x>/vueand/<x>/react. A React preset isdefineEditor({ 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
/serveror/nuxtfrom browser code. Servers use the framework-freeschema/protocol/typesentries (/ai/protocol,/integrations/transcript-ai/protocol,/tree/types,/settings).
After the change
Check the invariants and run the verification commands.
Overview
Machine entry points of this site (llms.txt, markdown twins, sitemap, installable Agent Skill) and where an agent starts.
Add a block or feature
Agent playbook for adding an own block or feature to @skaile/forge-editor: an ordered checklist where each step links to the tutorial chapter that shows it.