The editor is split into nine packages in a layered dependency direction.
| Package | Role |
|---|---|
@editorjs/model-types |
Shared low-level types, nominal brands, and base event classes (Index, BaseDocumentEvent, the model event classes, EventBus). No runtime dependencies of its own. |
@editorjs/sdk |
Shared contracts — interfaces, base event classes, EventBus |
@editorjs/model |
In-memory document model (EditorJSModel) |
@editorjs/dom-adapters |
Binds model nodes to DOM inputs; default adapter implementation |
@editorjs/collaboration-manager |
Operational transformation, batching, undo/redo, OT WebSocket client |
@editorjs/core |
Orchestrator — IoC container, plugin/tool lifecycle, EditorAPI; core services including local undo/redo via UndoRedoManager |
@editorjs/ui |
Default UI shell — EditorjsUI, BlocksUI, Toolbar, InlineToolbar, Toolbox |
@editorjs/ot-server |
Standalone WebSocket OT server — OTServer, DocumentManager |
playground |
Vite dev sandbox; not published |
model-typesis the foundation layer: it has no dependency onmodelorsdk, and onlymodelandsdkmay depend on it directly. Every other package (dom-adapters,collaboration-manager,ui, and tools/plugins in general) that needsIndex, event classes, or other model-types primitives should get them re-exported through@editorjs/sdkinstead of depending on@editorjs/model-typesdirectly. This exists somodelandsdkcan share the sameIndex/event/nominal-type definitions withoutsdkdepending on the fullmodelengine (and vice versa) — seepackages/model-types/src/index.tsfor the exact re-exported surface.sdkis the contract layer all other packages depend on.modelis the engine implementation that backsEditorJSModel. It is consumed directly bycore(the orchestrator) andot-server(server-side document state), but tools and plugins should never import@editorjs/modeldirectly — they should depend on@editorjs/sdk's contracts (BlockTool,InlineTool,Index, event types, etc.) instead.sdkre-exports everything frommodelthat a tool/plugin author legitimately needs, somodelitself isn't part of the stable, tool-facing API surface and is free to change its internals.corewires runtime dependencies; it should be the only orchestrator.modeldoes not depend on DOM concerns.dom-adaptersandcollaboration-managerobserve/apply model changes through public APIs and events, and depend only onsdk(notmodelormodel-types).uidepends onsdk; it is registered as anEditorjsPluginviacore.use().ot-serverdepends oncollaboration-manager(forOperation/ message types),model, andsdk; it runs server-side only.
Core is the entry point and owner of service wiring. Most services are wired in the constructor; core.use(...) registers UI plugins and tools; initialize() prepares tools, initializes the model, and starts collaboration.
BlocksUI owns the contenteditable blocks holder. It intercepts browser beforeinput events, normalises them into BeforeInputUIEvent, and dispatches them on the global EventBus. It also listens for BlockAddedCoreEvent / BlockRemovedCoreEvent to insert/remove rendered block elements in the DOM.
OTServer is a standalone Node.js WebSocket server. It maintains one DocumentManager per documentId. On each incoming Operation message it transforms the operation against any conflicting operations (ops with a higher or equal revision number), bumps the revision, applies the result to its own EditorJSModel copy, and broadcasts the transformed operation to all connected clients for that document.
Direct cross-layer coupling should be avoided: use interfaces/events from sdk and mutation APIs from EditorJSModel.
→ diagrams/architecture-overview.mmd
Package boundaries and integration contracts. Keep this as the high-level map; see other docs for per-subsystem flow details.