What Technology Powers the @plane/editor Package in Plane?
The @plane/editor package is built on TipTap (a headless ProseMirror wrapper), Yjs for real-time collaboration, and Floating UI for positioning, creating a rich-text editing engine used throughout the Plane project management platform.
The @plane/editor package serves as the rich-text editing backbone for Plane, an open-source project management platform. Understanding what technology powers the @plane/editor package reveals a modern architecture that combines proven editing frameworks with Conflict-free Replicated Data Types (CRDTs) for seamless multi-user collaboration.
Core Editing Engine: TipTap and ProseMirror
The foundation of @plane/editor relies on TipTap, a headless editor framework that wraps the robust ProseMirror library. This combination provides the document model, schema validation, and command infrastructure necessary for complex rich-text operations.
TipTap Core and ProseMirror Foundation
At the heart of the editor is @tiptap/core (declared in packages/editor/package.json at line 46), which supplies the low-level ProseMirror document model and schema system. The editor leverages TipTap's extension ecosystem—including @tiptap/extension-heading, @tiptap/extension-bold, @tiptap/extension-list, and table extensions—to assemble a comprehensive feature set. These extensions map directly to ProseMirror's RawCommands interface, allowing the editor to execute document transformations programmatically.
React Integration Layer
The package connects TipTap's imperative API to React's component model through @tiptap/react (line 65). This dependency exposes the useEditor hook and EditorContent component, enabling React components to instantiate and render the ProseMirror view. The editor maintains a Ref API that bridges React's declarative patterns with TipTap's command-based architecture.
Real-Time Collaboration Infrastructure
Beyond single-user editing, the package implements collaborative capabilities using a Yjs and Hocuspocus stack.
yjs (line 84) provides the CRDT-based state management that allows multiple users to edit the same document simultaneously without conflicts. The @hocuspocus/provider (line 39) layer handles WebSocket server/client communication, synchronizing Yjs document updates across connected clients in real time. This architecture enables features like live cursors and concurrent text editing in Plane's collaborative documents.
UI Positioning and Utility Libraries
The editor's interface components rely on Floating UI (@floating-ui/react and @floating-ui/dom at line 36) to calculate positioning for toolbars, pop-overs, and context menus relative to the cursor or selection. Additional utility libraries declared in packages/editor/package.json (lines 69-78) include:
lodash-esfor helper functionslinkifyjsfor automatic link detectionhighlight.jsandlowlightfor syntax highlighting in code blockstippy.jsfor tooltip management
Editor Components and API Architecture
The package exposes two primary editor variants and a comprehensive Ref-based API for programmatic control.
RichTextEditorWithRef and LiteTextEditorWithRef
The main entry points are implemented in distinct component files:
RichTextEditorWithRef(packages/editor/src/core/components/editors/rich-text/editor.tsx): Creates a full-featured TipTap instance with all extensions enabled, including tables, embeds, and collaborative features.- **
LiteTextEditorWithRef** ([packages/editor/src/core/components/editors/lite-text/editor.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/core/components/editors/lite-text/editor.tsx)): A lighter variant that disables heavy extensions while maintaining the same TipTap core foundation.
Both components wire the Yjs provider when collaborative configuration is present and render the ProseMirror view through the React integration layer.
CoreEditorRefApi Interface
The public API is defined in packages/editor/src/core/types/editor.ts through the CoreEditorRefApi type, which exposes methods for controlling the editor instance:
// Defined in packages/editor/src/core/types/editor.ts
export type CoreEditorRefApi = {
blur: () => void;
focus: (args: Parameters<RawCommands["focus"]>[0]) => void;
executeMenuItemCommand: <T extends TEditorCommands>(props: TCommandWithPropsWithItemKey<T>) => void;
emitRealTimeUpdate: (action: TDocumentEventsServer) => void;
// …other methods
};
This interface allows parent components to execute commands, trigger real-time updates, and manipulate focus without accessing the underlying TipTap instance directly. Plane-specific extensions to these types are maintained in packages/editor/src/ce/types/editor-extended.ts.
Implementation Examples
Creating a Rich-Text Editor
import { RichTextEditorWithRef } from '@plane/editor';
import type { IRichTextEditorProps, EditorRefApi } from '@plane/editor';
function MyPage() {
const editorRef = React.useRef<EditorRefApi>(null);
const props: IRichTextEditorProps = {
id: 'my-page',
initialValue: '',
editable: true,
fileHandler: async (file) => {/* upload logic */},
mentionHandler: {/* mention logic */},
getEditorMetaData: (html) => {/* meta parsing */},
// …other required props
};
return <RichTextEditorWithRef {...props} forwardedRef={editorRef} />;
}
Using the Ref API
// Assuming the ref from the previous example
if (editorRef.current) {
// Insert a table at the current cursor position
editorRef.current.executeMenuItemCommand({ itemKey: 'table' });
// Get the current Markdown representation
const markdown = editorRef.current.getMarkDown();
// Trigger a real-time update (e.g., after a custom embed)
editorRef.current.emitRealTimeUpdate({
type: 'embed',
payload: { url: 'https://example.com' },
});
}
Enabling Collaboration
import { CollaborativeDocumentEditorWithRef } from '@plane/editor';
import type { ICollaborativeDocumentEditorProps } from '@plane/editor';
function CollaborativePage() {
const collaborativeProps: ICollaborativeDocumentEditorProps = {
id: 'doc-123',
realtimeConfig: { provider: 'hocuspocus', doc: 'doc-123' },
user: { id: 'user-1', name: 'Alice' },
// …other required props
};
return <CollaborativeDocumentEditorWithRef {...collaborativeProps} />;
}
Summary
- TipTap and ProseMirror provide the foundational document model and editing engine in
@plane/editor, with core dependencies declared inpackages/editor/package.json. - Yjs and Hocuspocus enable real-time collaborative editing through CRDT-based state synchronization and WebSocket transport.
- Floating UI powers the positioning of contextual toolbars and menus relative to text selections.
- RichTextEditorWithRef and LiteTextEditorWithRef offer full-featured and lightweight editing experiences respectively, both exposing the
CoreEditorRefApifor programmatic control. - The editor architecture supports Plane-specific extensions through TypeScript definitions in the
ce(community edition) directory structure.
Frequently Asked Questions
What is the core technology behind @plane/editor?
The package is built on TipTap, a headless framework that wraps ProseMirror. According to the source code in packages/editor/package.json, the editor imports @tiptap/core to handle the document schema and @tiptap/react for React integration, providing a modern abstraction over ProseMirror's imperative API.
How does @plane/editor handle real-time collaboration?
Real-time collaboration is implemented using Yjs for Conflict-free Replicated Data Types (CRDTs) and Hocuspocus as the WebSocket provider. As configured in packages/editor/package.json, Yjs manages the shared document state while Hocuspocus synchronizes updates between clients, enabling simultaneous multi-user editing without locks or conflicts.
What is the difference between RichTextEditorWithRef and LiteTextEditorWithRef?
RichTextEditorWithRef (packages/editor/src/core/components/editors/rich-text/editor.tsx) instantiates a TipTap editor with all heavy extensions enabled, including tables, embeds, and full collaborative features. LiteTextEditorWithRef (packages/editor/src/core/components/editors/lite-text/editor.tsx) uses the same TipTap core but disables resource-intensive extensions, offering a streamlined editing experience for simpler text inputs.
How can I programmatically execute commands in the editor?
Access the editor's imperative API through the React ref using CoreEditorRefApi. The executeMenuItemCommand method accepts a command key (such as 'table' or 'bold') and executes the corresponding TipTap command. You can also trigger real-time updates via emitRealTimeUpdate or retrieve Markdown content through getMarkDown() as defined in packages/editor/src/core/types/editor.ts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →