Can Plane Be Extended with Custom Plugins? A Guide to TipTap Extensions
Yes, Plane supports custom plugins through its TipTap-based editor architecture, allowing developers to pass custom extensions via the extensions prop in the @plane/editor package.
Plane’s web editor is built on TipTap, a ProseMirror-based rich-text editor framework that provides a robust extension API. As an open-source project, Plane (makeplane/plane) exposes this extensibility directly to developers, enabling deep customization of the editing experience without modifying core internals. You can extend Plane with custom plugins by providing standard TipTap extensions to the editor component.
How Plane’s Editor Architecture Supports Plugins
Plane’s editor component lives in the packages/editor package and is designed as a pure React component that accepts an extensions prop. According to the documentation in packages/editor/Readme.md, this prop accepts an array of TipTap extensions that are merged with the editor's built-in configuration.
Internally, the editor aggregates extensions in packages/editor/src/core/extensions/index.ts, where each extension registers as a TipTap plugin using new Plugin({ … }). This architecture means any valid TipTap extension—whether official, community-built, or custom-authored—can be injected into Plane's editor instance.
Creating Custom Extensions for Plane
Custom extensions in Plane are standard JavaScript or TypeScript objects that conform to TipTap’s Extension interface. You can implement three primary extension types to modify behavior:
Adding Custom Mark Extensions
To introduce new formatting options like text highlighting, create a Mark extension that defines how the mark is parsed, rendered, and toggled:
// my-extensions/highlight.ts
import { Extension } from '@tiptap/core';
import { Plugin, PluginKey } from 'prosemirror-state';
import { Mark } from '@tiptap/extension-mark';
export const HighlightExtension = Mark.create({
name: 'highlight',
parseHTML() {
return [{ tag: 'span[data-highlight]' }];
},
renderHTML({ HTMLAttributes }) {
return ['span', { ...HTMLAttributes, 'data-highlight': true }, 0];
},
addCommands() {
return {
toggleHighlight:
() =>
({ commands }) => {
return commands.toggleMark(this.name);
},
};
},
});
Then pass the extension to the Editor component when rendering:
// app.tsx – where the Plane editor is used
import { Editor } from '@plane/editor';
import { HighlightExtension } from './my-extensions/highlight';
function MyPage() {
return (
<Editor
// Existing config is spread automatically; we add our extension
extensions={[HighlightExtension]}
// …other required props (e.g., value, onChange)…
/>
);
}
Hooking into the Transaction Pipeline
For advanced use cases like intercepting paste events or enforcing business rules, create an Extension that registers ProseMirror plugins:
// my-extensions/file-upload.ts
import { Extension } from '@tiptap/core';
import { Plugin, PluginKey } from 'prosemirror-state';
export const FileUploadExtension = Extension.create({
name: 'fileUpload',
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('fileUpload'),
props: {
handlePaste(view, event) {
// Custom logic to intercept pasted files and insert them
// …
return false; // let other handlers run if we didn't handle it
},
},
}),
];
},
});
Integrate this the same way by including it in the extensions array:
// app.tsx
import { Editor } from '@plane/editor';
import { FileUploadExtension } from './my-extensions/file-upload';
function MyPage() {
return (
<Editor
extensions={[FileUploadExtension]}
// …
/>
);
}
Extending the Editor UI
The editor's menu system in packages/editor/src/core/components/menus/menu-items.ts provides patterns for adding toolbar buttons and context-menu items. When creating extensions that require UI controls, leverage this existing menu infrastructure to maintain consistency with Plane's design system.
Key Source Files for Plugin Development
Understanding these specific files in the Plane repository helps when building custom plugins:
packages/editor/Readme.md: Documents the public API including theextensionsprop that enables custom plugin support.packages/editor/src/core/extensions/index.ts: Shows how built-in extensions are registered and where your custom extensions merge into the configuration.packages/editor/src/core/plugins/*.ts: Contains reference implementations likemarkdown-clipboard.tsanddrag-handle.tsthat demonstrate ProseMirror plugin patterns.packages/editor/src/core/components/menus/menu-items.ts: Defines toolbar and menu UI components for extending the editor interface.packages/editor/src/index.ts: The main export where theEditorReact component receives yourextensionsarray.
Summary
- Plane's editor is built on TipTap and ProseMirror, making it inherently extensible through standard extension objects.
- Pass custom plugins via the
extensionsprop documented inpackages/editor/Readme.md. - Extensions can add new node types, intercept transactions via ProseMirror plugins, and integrate with Plane's menu system.
- No core modification is required; plugins live in your codebase and import into the Editor component.
- Reference implementations exist in
packages/editor/src/core/plugins/for guidance on building advanced plugins.
Frequently Asked Questions
What plugin system does Plane use?
Plane uses TipTap extensions, which are built on top of ProseMirror plugins. This allows you to use any standard TipTap extension or create custom ones using the @tiptap/core API. The editor accepts these via the extensions prop as documented in packages/editor/Readme.md.
Can I add custom toolbar buttons to Plane's editor?
Yes. You can extend the UI by creating extensions that interact with Plane's menu system defined in packages/editor/src/core/components/menus/menu-items.ts. Your custom extension can register commands that are then bound to toolbar buttons or context menu items within the editor interface.
Is it possible to intercept paste events in Plane?
Yes. By creating a custom extension that returns a ProseMirror plugin via addProseMirrorPlugins(), you can hook into the handlePaste event in the plugin's props. This allows you to intercept pasted content, handle file uploads, or transform clipboard data before it enters the document.
Do custom plugins require rebuilding Plane from source?
No. Because the Plane editor exports a pure React component from packages/editor/src/index.ts, you can import it into any external codebase, define your custom extensions locally, and pass them through the extensions prop without modifying the Plane repository itself.
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 →