> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumen.bjanczak.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Extend Lumen Editor with Custom Plugins

> Build a plugin function that accesses the editor API and DOM, subscribes to events, and install it via the plugins option or editor.use() after init.

Lumen Editor's plugin system lets you extend the editor with your own behavior — status bars, word counts, character limits, custom keyboard shortcuts, analytics hooks, and more — without forking or monkey-patching the core library. A plugin is just a plain function, so there's nothing new to learn: if you can write JavaScript, you can write a plugin.

## Plugin shape

A plugin is a function that receives the editor instance and returns nothing:

```ts theme={null}
(editor: Editor) => void
```

That's the entire contract. You can read from the editor, write to the DOM, subscribe to events, or call any public editor method — all from within that single function. There are no lifecycle hooks, no class to extend, and no registration API beyond passing your function to the `plugins` option.

<Note>
  Plugins run **after** the editor has fully mounted and its DOM is ready. This means `editor.root` is available and all built-in modules have already been initialized when your plugin function executes.
</Note>

## Full example: word count plugin

The following plugin appends a live word-count display beneath the editor and updates it on every change:

```js theme={null}
function wordCount(editor) {
  // Create and append the display element
  const el = document.createElement('div');
  el.className = 'wc';
  editor.root.appendChild(el);

  // Update on every content change
  editor.on('change', () => {
    const words = editor
      .getText()
      .trim()
      .split(/\s+/)
      .filter(Boolean).length;

    el.textContent = words + ' words';
  });
}

const editor = new Editor('#e', {
  plugins: [wordCount]
});
```

## Accessing the DOM

Inside your plugin, `editor.root` is the live `contenteditable` element that the editor manages. You can append child elements to it, add event listeners, or read its `innerHTML` and `textContent`:

```js theme={null}
function highlightEmpty(editor) {
  editor.on('change', () => {
    if (!editor.getText().trim()) {
      editor.root.classList.add('is-empty');
    } else {
      editor.root.classList.remove('is-empty');
    }
  });
}
```

<Warning>
  Avoid mutating `editor.root.innerHTML` directly — doing so bypasses the editor's internal state and history stack, which can lead to inconsistent undo/redo behavior. Use the editor's public API methods instead when you need to change content programmatically.
</Warning>

## Subscribing to events

Use `editor.on(eventName, handler)` inside your plugin to react to editor events. All of the editor's built-in events are available:

```js theme={null}
function analyticsPlugin(editor) {
  editor.on('change', (html) => {
    analytics.track('editor:change', { length: html.length });
  });

  editor.on('autosave', () => {
    analytics.track('editor:autosave');
  });

  editor.on('error', ({ code, message }) => {
    analytics.track('editor:error', { code, message });
  });
}
```

| Event      | Payload             | When it fires                 |
| ---------- | ------------------- | ----------------------------- |
| `change`   | `html: string`      | Every content change          |
| `autosave` | —                   | Content saved to localStorage |
| `error`    | `{ code, message }` | Any editor error              |

## Installing plugins

You can install plugins in two ways:

<Steps>
  <Step title="Via the plugins option at initialization">
    Pass an array of plugin functions to the `plugins` option. All plugins in the array run once, in order, after the editor mounts:

    ```js theme={null}
    const editor = new Editor('#editor', {
      plugins: [wordCount, analyticsPlugin, highlightEmpty]
    });
    ```
  </Step>

  <Step title="Via editor.use() after initialization">
    Call `editor.use(plugin)` at any point after the editor has been constructed. The plugin function runs immediately:

    ```js theme={null}
    const editor = new Editor('#editor', { /* ... */ });

    // Install a plugin later, e.g. after lazy-loading it
    import('./plugins/spellcheck.js').then(({ spellcheck }) => {
      editor.use(spellcheck);
    });
    ```
  </Step>
</Steps>

<Tip>
  For reusable plugins that accept configuration, wrap them in a factory function that returns the `(editor) => void` function. For example: `createWordCount({ className: 'my-wc' })` returns a configured plugin function you can pass directly to the `plugins` array.
</Tip>
