Guides
Plugin System
Built-in modules, custom formats, serializers, and registration without forking core.
Modules
Core (always loaded)
Clipboard
Normalizes paste from HTML, Word, Google Docs, etc. Add custom matchers:
modules: {
clipboard: {
matchers: [
['B', (node, delta) => delta.insert(node.textContent, { bold: true })],
],
},
}Keyboard
Default bindings for Enter in lists/headers, tab indent, link shortcuts (snow theme adds Ctrl/Cmd+K). Extend:
editor.keyboard.addBinding({ key: 'b', shortKey: true }, (range, context) => {
editor.format('bold', !context.format.bold);
});History
| Option | Default | Description |
|---|---|---|
delay | 1000 | ms before merging undo steps |
maxStack | 100 | Max undo depth |
userOnly | false | Ignore API changes |
Uploader
Handles drag-and-drop and cooperates with the toolbar image button. See Configuration.
Optional
Toolbar
Renders formatting controls. Theme provides default handlers for image, video, and formula.
Snow theme auto-builds a default toolbar when modules.toolbar is set without a container.
Syntax
Requires highlight.js. Adds a language <select> on code blocks and debounced highlighting.
Table
Registers table blots. Keyboard shortcuts for navigation inside cells when the table module is active.
const table = editor.getModule('table');
table.insertTable(2, 3);
table.deleteRow();
table.deleteColumn();
table.deleteTable();No default toolbar button — add a custom handler if needed.
Image resize
Shows a resize handle when a single image embed is selected.
| Option | Default |
|---|---|
minWidth | 48 |
maxWidth | null (editor width) |
Does not affect video or other embeds.
The overlay mounts on .lxr-container (sibling of the scroll surface) and tracks the image via DOM coordinates after layout settles. Call editor.destroy() when unmounting the editor to avoid orphan overlays in SPA remounts.
API details: API reference — imageResize
Authoring a module
Modules extend Module from lextrix-core and register with lxrPath.module():
import Module from 'lextrix-core/core/module.js';
import Lextrix, { lxrPath } from 'lextrix';
class WordCountModule extends Module {
constructor(lextrix, options) {
super(lextrix, options);
lextrix.on('text-change', () => this.update());
}
update() {
console.log('Document length:', this.lextrix.getLength());
}
}
Lextrix.register({ [lxrPath.module('wordCount')]: WordCountModule });
new Lextrix('#editor', {
theme: 'snow',
modules: { wordCount: true },
});Lifecycle: theme loads modules from options → pluginHost.register() → pluginHost.bindAll(editor).
Access instances with editor.getModule('wordCount').
Study existing modules in packages/modules/src/modules/. Source: packages/core/src/core/plugins/plugin-host.ts.
Formats
Built-in formats live in lextrix-formats. Custom formats register a blot class and optional attributor metadata.
Registration
Extensions register through Lextrix.register() using lxr/* paths:
import Lextrix, { lxrPath } from 'lextrix';
lxrPath.format('callout'); // lxr/formats/callout
lxrPath.module('mentions'); // lxr/modules/mentions
lxrPath.blot('scroll'); // lxr/blots/scroll
lxrPath.theme('snow'); // lxr/themes/snow
lxrPath.attributor('block', 'align'); // lxr/attributors/block/alignBare paths like formats/bold or legacy keys like parchment throw at runtime.
Lextrix.register({ [lxrPath.format('callout')]: CalloutBlot });defineInlineTagFormat and other format helpers are available when developing inside the monorepo (lextrix-formats). npm consumers implement a blot class and register it as above. See Framework integration.
Inline tag format (monorepo)
import { defineInlineTagFormat } from 'lextrix-formats/inline-format.js';
export const Highlight = defineInlineTagFormat({
blotName: 'highlight',
tagName: 'MARK',
});
Lextrix.register({ [lxrPath.format('highlight')]: Highlight });Block format
import { defineBlockFormat } from 'lextrix-formats/block-format.js';
export const Callout = defineBlockFormat({
blotName: 'callout',
tagName: 'DIV',
className: 'lxr-callout',
});Attributor formats
For class, style, or attribute-based formatting (align, color, indent):
import {
defineClassAttributorFormat,
defineAttributorGroup,
} from 'lextrix-formats/attributor-format.js';
const MarginClass = defineClassAttributorFormat('margin', 'lxr-margin', {
scope: Scope.BLOCK,
whitelist: ['small', 'large'],
});
defineAttributorGroup('margin', [MarginClass]);Embeds
Embeds are non-text leaf nodes (image, video, formula):
{ "insert": { "image": "https://example.com/photo.png" } }editor.insertEmbed(index, 'image', url, 'user');Define an embed blot extending the embed base. Study built-ins in packages/formats/src/formats/image.ts, video.ts, formula.ts.
| Kind | Behavior |
|---|---|
| Inline embed | Single leaf in a line (image, formula) |
| Block embed | Own block row (video) |
Custom embeds may need clipboard matchers for non-standard pasted HTML. See packages/modules/src/modules/clipboard.ts.
Format metadata hooks
defineDocumentFormat attaches Lextrix-native metadata to a blot class:
| Hook | When it runs |
|---|---|
optimize | Early in the optimize pass |
postOptimize | After structure enforcement |
Scope
Formats declare a Scope bitmask (block, inline, attribute, embed).
Examples
packages/formats/src/formats/bold.ts,align.ts,blockquote.ts- Tests:
packages/lextrix/test/unit/formats/