Skip to main content
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().