Skip to main content

Lexical Plugins

tip

Most of these plugins are available as modern Lexical Extensions, which do not require separate configuration and can have more efficient implementations. You should prefer using extensions over plugins where available, and migrate any project to extensions with LexicalExtensionComposer or LexicalExtensionEditorComposer if it is still using LexicalComposer.

React-based plugins use a Lexical editor instance from <LexicalExtensionComposer> or <LexicalComposer> (legacy) context:

import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
const initialConfig = {
namespace: 'MyEditor',
theme,
onError,
};

<LexicalComposer initialConfig={initialConfig}>
<PlainTextPlugin
contentEditable={<ContentEditable />}
placeholder={<div>Enter some text...</div>}
/>
<HistoryPlugin />
<OnChangePlugin onChange={onChange} />
...
</LexicalComposer>;

Note: Many plugins might require you to register the one or many Lexical nodes in order for the plugin to work. You can do this by passing a reference to the node to the nodes array in your initial editor configuration.

const initialConfig = {
namespace: 'MyEditor',
theme,
nodes: [ListNode, ListItemNode], // Pass the references to the nodes here
onError,
};

LexicalPlainTextPlugin

React wrapper for @lexical/plain-text that adds major features for plain text editing, including typing, deletion and copy/pasting.

<PlainTextPlugin
contentEditable={
<ContentEditable
aria-placeholder={'Enter some text...'}
placeholder={<div>Enter some text...</div>}
/>
}
ErrorBoundary={LexicalErrorBoundary}
/>
tip

Use PlainTextExtension when using extensions

LexicalRichTextPlugin

React wrapper for @lexical/rich-text that adds major features for rich text editing, including typing, deletion, copy/pasting, indent/outdent and bold/italic/underline/strikethrough text formatting.

<RichTextPlugin
contentEditable={
<ContentEditable
aria-placeholder={'Enter some text...'}
placeholder={<div>Enter some text...</div>}
/>
}
ErrorBoundary={LexicalErrorBoundary}
/>
tip

Use RichTextExtension when using extensions

LexicalAutoFocusPlugin

Automatically focuses the editor when the component mounts and optionally places the cursor at the start or end of the root content.

<AutoFocusPlugin defaultSelection="rootEnd" />
tip

Use AutoFocusExtension when using extensions

LexicalOnChangePlugin

Plugin that calls onChange whenever Lexical state is updated. Using ignoreHistoryMergeTagChange (true by default) and ignoreSelectionChange (false by default) can give more granular control over changes that are causing onChange call.

<OnChangePlugin onChange={onChange} />
tip

When using extensions, subscribe to EditorStateExtension for a signal-based equivalent

LexicalHistoryPlugin

React wrapper for @lexical/history that adds support for history stack management and undo / redo commands.

<HistoryPlugin />
tip

Use HistoryExtension when using extensions

LexicalLinkPlugin

React wrapper for @lexical/link that adds support for links, including $toggleLink command support that toggles link for selected text.

<LinkPlugin />
tip

Use LinkExtension when using extensions

LexicalClickableLinkPlugin

Makes LinkNode elements in the editor clickable, navigating to the link URL on click. Set disabled to true to turn off the behavior, or newTab to control whether links open in a new tab.

Note: Requires LinkNode from @lexical/link to be registered.

<ClickableLinkPlugin newTab={true} disabled={false} />
tip

Use ClickableLinkExtension when using extensions

LexicalListPlugin

React wrapper for @lexical/list that adds support for lists (ordered and unordered)

<ListPlugin />
tip

Use ListExtension when using extensions

LexicalCheckListPlugin

React wrapper for @lexical/list that adds support for check lists. Note that it requires some css to render check/uncheck marks. See PlaygroundEditorTheme.css for details.

<CheckListPlugin />
tip

Use CheckListExtension when using extensions

LexicalTablePlugin

See API Documentation

React wrapper for @lexical/table that adds support for tables.

<TablePlugin />
tip

Use TableExtension when using extensions

LexicalHorizontalRulePlugin

Registers a command handler for INSERT_HORIZONTAL_RULE_COMMAND. When dispatched, it inserts a HorizontalRuleNode at the current selection.

Note: Requires HorizontalRuleNode to be registered. This plugin is deprecated in favor of HorizontalRuleExtension from @lexical/extension.

import {HorizontalRuleNode} from '@lexical/react/LexicalHorizontalRuleNode';

const config = {nodes: [HorizontalRuleNode]};

<HorizontalRulePlugin />
tip

Use HorizontalRuleExtension when using extensions

LexicalTabIndentationPlugin

Plugin that allows tab indentation in combination with @lexical/rich-text.

<TabIndentationPlugin />
tip

Use TabIndentationExtension when using extensions

LexicalHashtagPlugin

Automatically detects hashtag patterns (e.g., #hello) in text as the user types and converts them into HashtagNode instances with distinct styling.

Note: Requires HashtagNode from @lexical/hashtag to be registered.

import {HashtagNode} from '@lexical/hashtag';

const config = {nodes: [HashtagNode]};

<HashtagPlugin />
tip

Use HashtagExtension when using extensions

LexicalAutoLinkPlugin

Plugin will convert text into links based on passed matchers list. In example below whenever user types url-like string it will automatically convert it into a link node

const URL_MATCHER =
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;

const MATCHERS = [
(text) => {
const match = URL_MATCHER.exec(text);
if (match === null) {
return null;
}
const fullMatch = match[0];
return {
index: match.index,
length: fullMatch.length,
text: fullMatch,
url: fullMatch.startsWith('http') ? fullMatch : `https://${fullMatch}`,
// attributes: { rel: 'noreferrer', target: '_blank' }, // Optional link attributes
};
},
];

...

<AutoLinkPlugin matchers={MATCHERS} />
tip

Use AutoLinkExtension when using extensions

LexicalAutoEmbedPlugin

Watches for pasted links that match any of the provided embed configurations (e.g., YouTube, Twitter URLs). When a match is found, it shows a menu offering to replace the link with an embedded node.

Note: Requires LinkNode and AutoLinkNode from @lexical/link to be registered.

import {LexicalAutoEmbedPlugin, EmbedConfig, AutoEmbedOption} from '@lexical/react/LexicalAutoEmbedPlugin';

const YouTubeEmbedConfig = {
type: 'youtube',
parseUrl: (url) => {
const match = url.match(/youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/);
return match ? {url, id: match[1]} : null;
},
insertNode: (editor, result) => {
// Insert your custom embed node here
},
};

<LexicalAutoEmbedPlugin
embedConfigs={[YouTubeEmbedConfig]}
onOpenEmbedModalForConfig={(config) => {/* open modal */}}
getMenuOptions={(config, embedFn, dismissFn) => [
new AutoEmbedOption('Embed', {onSelect: embedFn}),
new AutoEmbedOption('Dismiss', {onSelect: dismissFn}),
]}
/>

LexicalCharacterLimitPlugin

Tracks the number of characters in the editor and renders a count of remaining characters. Uses OverflowNode to visually indicate when the limit is exceeded.

Note: Requires OverflowNode from @lexical/overflow to be registered.

import {CharacterLimitPlugin} from '@lexical/react/LexicalCharacterLimitPlugin';

<CharacterLimitPlugin charset="UTF-16" maxLength={280} />
tip

Use OverflowExtension when using extensions

LexicalClearEditorPlugin

Adds clearEditor command support to clear editor's content.

<ClearEditorPlugin />
tip

Use ClearEditorExtension when using extensions

LexicalMarkdownShortcutPlugin

Adds markdown shortcut support: headings, lists, code blocks, quotes, links and inline styles (bold, italic, strikethrough).

<MarkdownShortcutPlugin />

LexicalTableOfContentsPlugin

This plugin allows you to render a table of contents for a page from the headings from the editor. It listens to any deletions or modifications to those headings and updates the table of contents. Additionally, it's able to track any newly added headings and inserts them in the table of contents once they are created. This plugin also supports lazy loading - so you can defer adding the plugin until when the user needs it.

In order to use TableOfContentsPlugin, you need to pass a callback function in its children. This callback function gives you access to the up-to-date data of the table of contents. You can access this data through a single parameter for the callback which comes in the form of an array of arrays [[headingKey, headingTextContent, headingTag], [], [], ...]

headingKey: Unique key that identifies the heading. headingTextContent: A string of the exact text of the heading. headingTag: A string that reads either 'h1', 'h2', or 'h3'.

<TableOfContentsPlugin>
{(tableOfContentsArray) => {
return (
<MyCustomTableOfContentsPlugin tableOfContents={tableOfContentsArray} />
);
}}
</TableOfContentsPlugin>

LexicalEditorRefPlugin

Allows you to get a ref to the underlying editor instance outside of LexicalComposer, which is convenient when you want to interact with the editor from a separate part of your application.

const editorRef = useRef(null);
<EditorRefPlugin editorRef={editorRef} />;

LexicalCollaborationPlugin

Integrates Yjs-based real-time collaborative editing into Lexical. Creates a Yjs binding between the editor state and a shared Yjs document, renders remote user cursors, and provides collaborative undo/redo history.

import {CollaborationPlugin} from '@lexical/react/LexicalCollaborationPlugin';
import {WebsocketProvider} from 'y-websocket';
import {Doc} from 'yjs';

function createProvider(id, yjsDocMap) {
const doc = new Doc();
yjsDocMap.set(id, doc);
return new WebsocketProvider('ws://localhost:1234', id, doc);
}

<CollaborationPlugin
id="my-document"
providerFactory={createProvider}
shouldBootstrap={true}
username="Alice"
cursorColor="#FF0000"
/>

LexicalDraggableBlockPlugin

Adds a draggable block handle that appears next to top-level block nodes when hovered. Users can drag and drop blocks to reorder them. A target line indicates the drop position.

Note: This plugin is experimental.

import {DraggableBlockPlugin_EXPERIMENTAL} from '@lexical/react/LexicalDraggableBlockPlugin';

const menuRef = useRef(null);
const targetLineRef = useRef(null);

<DraggableBlockPlugin_EXPERIMENTAL
anchorElem={document.body}
menuRef={menuRef}
targetLineRef={targetLineRef}
menuComponent={<div ref={menuRef} className="drag-handle">::</div>}
targetLineComponent={<div ref={targetLineRef} className="target-line" />}
isOnMenu={(el) => menuRef.current?.contains(el) ?? false}
/>

LexicalNodeEventPlugin

Attaches a DOM event listener on the editor root element that fires only when the event target corresponds to a node of the specified type.

import {NodeEventPlugin} from '@lexical/react/LexicalNodeEventPlugin';

<NodeEventPlugin
nodeType={ImageNode}
eventType="click"
eventListener={(event, editor, nodeKey) => {
console.log('Clicked on node:', nodeKey);
}}
/>

LexicalNodeMenuPlugin

Renders a menu anchored to a specific Lexical node identified by nodeKey. Provides keyboard navigation (arrow keys, enter, escape) through the menu options.

import {LexicalNodeMenuPlugin, MenuOption} from '@lexical/react/LexicalNodeMenuPlugin';

<LexicalNodeMenuPlugin
nodeKey={selectedNodeKey}
options={menuOptions}
onSelectOption={(option, textNode, closeMenu) => {
option.action();
closeMenu();
}}
menuRenderFn={(anchorRef, {options, selectedIndex, selectOptionAndCleanUp}) =>
anchorRef.current
? createPortal(
<ul>
{options.map((opt, i) => (
<li key={opt.key} onClick={() => selectOptionAndCleanUp(opt)}>
{opt.title}
</li>
))}
</ul>,
anchorRef.current,
)
: null
}
onClose={() => setSelectedNodeKey(null)}
/>

LexicalTypeaheadMenuPlugin

Provides a typeahead/autocomplete menu that appears when the user types a trigger character (e.g., @ for mentions, : for emoji). It listens to text changes, runs a trigger function against text before the cursor, and when a match is found, positions a menu at the trigger location.

import {
LexicalTypeaheadMenuPlugin,
useBasicTypeaheadTriggerMatch,
} from '@lexical/react/LexicalTypeaheadMenuPlugin';

const triggerMatch = useBasicTypeaheadTriggerMatch('@', {minLength: 1});

<LexicalTypeaheadMenuPlugin
triggerFn={triggerMatch}
options={mentionOptions}
onQueryChange={setQuery}
onSelectOption={(option, textNode, closeMenu) => {
editor.update(() => {
const mentionNode = $createMentionNode(option.name);
if (textNode) {
textNode.replace(mentionNode);
}
closeMenu();
});
}}
menuRenderFn={(anchorRef, {options, selectedIndex, selectOptionAndCleanUp}) =>
anchorRef.current
? createPortal(
<ul className="typeahead-menu">
{options.map((opt, i) => (
<li
key={opt.key}
className={i === selectedIndex ? 'selected' : ''}
onClick={() => selectOptionAndCleanUp(opt)}>
{opt.name}
</li>
))}
</ul>,
anchorRef.current,
)
: null
}
/>

LexicalSelectionAlwaysOnDisplay

By default, browser text selection becomes invisible when clicking away from the editor. This plugin ensures the selection remains visible.

<SelectionAlwaysOnDisplay />
tip

Use SelectionAlwaysOnDisplayExtension when using extensions