Plugins are the primary extension point for Lumen Editor. A plugin is a plain function that receives the fully initialized editor instance — nothing more, nothing less. Because every editor method and event is accessible from inside a plugin, you can build anything from a word-count sidebar to a collaborative cursor indicator without touching editor internals.
Plugin Signature
A plugin conforms to the EditorPlugin type:
type EditorPlugin = (editor: Editor) => void;
The function receives the Editor instance and should perform all its setup work synchronously — subscribing to events, appending UI elements, patching methods, etc. The return value is ignored.
import { Editor, EditorPlugin } from 'lumen-editor';
const myPlugin: EditorPlugin = (editor) => {
// Set up your plugin here
};
What Plugins Can Do
Because plugins receive the full editor instance, they can:
- Subscribe to events using
editor.on() and clean up with editor.off() to react to content changes, errors, view switches, and more.
- Read and write content by calling
editor.getHTML(), editor.getText(), and editor.setHTML().
- Append UI to the editor root by accessing
editor.root — the underlying contenteditable element — and inserting sibling or wrapper nodes.
- Call any public method including
editor.focus(), editor.blur(), editor.setTheme(), and editor.use() to compose behaviour across plugins.
- Add side effects such as starting a
MutationObserver, registering keyboard shortcuts via document.addEventListener, or initializing third-party libraries that operate on the editor’s DOM.
Installing a Plugin
Via the constructor
Pass an array of plugin functions to the plugins option. Plugins run synchronously in array order immediately after the editor mounts:
import { Editor } from 'lumen-editor';
import 'lumen-editor/theme.css';
function analyticsPlugin(editor) {
editor.on('change', () => {
analytics.track('editor_change');
});
}
function autoLinkPlugin(editor) {
editor.on('change', (html) => {
// auto-link detection logic …
});
}
const editor = new Editor('#editor', {
plugins: [analyticsPlugin, autoLinkPlugin],
});
Via editor.use()
Install a plugin at any point after initialization using editor.use(). This is useful for lazy-loading plugins or for conditionally applying them based on runtime state:
import { wordCountPlugin } from './plugins/wordCount';
// Install immediately
editor.use(wordCountPlugin);
// Or install after an async check
const flags = await fetchFeatureFlags();
if (flags.enableWordCount) {
editor.use(wordCountPlugin);
}
Plugins run synchronously — whether registered in the plugins constructor option or installed via editor.use(). All setup code inside a plugin executes before the next JavaScript statement after the constructor or use() call returns.
Full Example: Word Count Plugin
The following plugin appends a live word count badge below the editor and keeps it updated on every content change:
function wordCountPlugin(editor) {
// Create the badge element
const badge = document.createElement('div');
badge.className = 'wc';
// Append it inside the editor root
editor.root.appendChild(badge);
// Keep it in sync with content changes
editor.on('change', () => {
badge.textContent =
editor.getText().trim().split(/\s+/).filter(Boolean).length + ' words';
});
}
// Install via options
const editor = new Editor('#editor', {
plugins: [wordCountPlugin],
});
// — or install after the fact —
editor.use(wordCountPlugin);
Accessing the Editor Root
editor.root is the contenteditable HTMLElement that hosts the document. You can use it as an anchor for additional DOM nodes, event delegation, or position-aware UI like floating toolbars:
function floatingMenuPlugin(editor) {
const menu = document.createElement('div');
menu.className = 'floating-menu';
menu.hidden = true;
// Position the menu relative to the editor root
document.body.appendChild(menu);
editor.on('selectionchange', () => {
const sel = window.getSelection();
if (!sel || sel.isCollapsed) {
menu.hidden = true;
return;
}
const range = sel.getRangeAt(0);
const rect = range.getBoundingClientRect();
menu.style.setProperty('--menu-top', `${rect.top + window.scrollY - 40}px`);
menu.style.setProperty('--menu-left', `${rect.left}px`);
menu.hidden = false;
});
}
If your plugin appends DOM nodes, consider removing them inside a cleanup handler. While Lumen Editor does not provide a formal plugin teardown hook, you can subscribe to a signal of your own (e.g. a custom event or a shared AbortSignal) to remove nodes and unsubscribe event listeners before calling editor.destroy().