Overview
In Lexical’s React integration, plugins are components that hook into the editor lifecycle to add functionality. They typically return null but register listeners, commands, and transforms during their mount phase.
Plugin Architecture
Plugins follow React’s component lifecycle:
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' ;
import { useEffect } from 'react' ;
function MyPlugin () {
const [ editor ] = useLexicalComposerContext ();
useEffect (() => {
// Setup: register listeners, commands, transforms
const unregister = editor . registerUpdateListener (({ editorState }) => {
// React to editor updates
});
// Cleanup: automatically called on unmount
return unregister ;
}, [ editor ]);
return null ; // Plugins typically don't render anything
}
Core Plugins
RichTextPlugin
Enables rich text editing with support for headings, lists, formatting, and more.
The ContentEditable component where users type.
placeholder
JSX.Element | ((isEditable: boolean) => JSX.Element)
Placeholder content shown when the editor is empty.
ErrorBoundary
ErrorBoundaryType
required
Error boundary component for handling rendering errors.
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin' ;
import { ContentEditable } from '@lexical/react/LexicalContentEditable' ;
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary' ;
function Editor () {
return (
< RichTextPlugin
contentEditable = {
< ContentEditable
className = "editor-input"
aria-placeholder = "Enter text..."
placeholder = {
< div className = "editor-placeholder" > Enter text... </ div >
}
/>
}
ErrorBoundary = { LexicalErrorBoundary }
/>
);
}
PlainTextPlugin
Enables plain text editing without rich text features.
import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin' ;
import { ContentEditable } from '@lexical/react/LexicalContentEditable' ;
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary' ;
function Editor () {
return (
< PlainTextPlugin
contentEditable = { < ContentEditable className = "editor-input" /> }
placeholder = { < div > Enter plain text... </ div > }
ErrorBoundary = { LexicalErrorBoundary }
/>
);
}
HistoryPlugin
Adds undo/redo functionality.
Delay in milliseconds before creating a new history entry.
External history state for synchronizing undo/redo across multiple editors.
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin' ;
< HistoryPlugin delay = { 1000 } />
OnChangePlugin
Executes a callback when the editor state changes.
onChange
(editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void
required
Callback invoked on editor state changes.
ignoreHistoryMergeTagChange
Whether to ignore changes tagged with HISTORY_MERGE_TAG.
Whether to ignore changes that only affect selection.
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin' ;
function Editor () {
const handleChange = ( editorState , editor , tags ) => {
editorState . read (() => {
const json = editorState . toJSON ();
// Save to database, localStorage, etc.
});
};
return < OnChangePlugin onChange = { handleChange } /> ;
}
AutoFocusPlugin
Automatically focuses the editor on mount.
Where to place the cursor when focusing.
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin' ;
< AutoFocusPlugin defaultSelection = "rootStart" />
Feature Plugins
ListPlugin
Enables ordered lists, unordered lists, and checklists.
Enforces strict indentation rules for list items.
Preserves numbering continuity when splitting numbered lists.
import { ListPlugin } from '@lexical/react/LexicalListPlugin' ;
import { ListNode , ListItemNode } from '@lexical/list' ;
// Add to initialConfig.nodes:
// nodes: [ListNode, ListItemNode]
< ListPlugin hasStrictIndent shouldPreserveNumbering />
LinkPlugin
Enables link functionality.
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin' ;
import { LinkNode } from '@lexical/link' ;
// Add to initialConfig.nodes:
// nodes: [LinkNode]
< LinkPlugin />
AutoLinkPlugin
Automatically converts URLs to links as you type.
matchers
Array<LinkMatcher>
required
Array of matchers that define URL patterns to auto-link.
Callback invoked when a link is created or modified.
import { AutoLinkPlugin , createLinkMatcherWithRegExp } from '@lexical/react/LexicalAutoLinkPlugin' ;
import { AutoLinkNode } from '@lexical/link' ;
// Add to initialConfig.nodes:
// nodes: [AutoLinkNode]
const URL_MATCHER =
/ (( https ? : \/\/ ( www \. ) ? ) | (( www \. ))) [ \w\W ] + [ \/ \w \W -_~?&=%. ] + / i ;
const MATCHERS = [
createLinkMatcherWithRegExp ( URL_MATCHER , ( text ) => {
return text . startsWith ( 'http' ) ? text : `https:// ${ text } ` ;
}),
];
< AutoLinkPlugin matchers = { MATCHERS } />
MarkdownShortcutPlugin
Enables Markdown shortcuts (e.g., **bold**, # Heading).
import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin' ;
import { TRANSFORMERS } from '@lexical/markdown' ;
< MarkdownShortcutPlugin transformers = { TRANSFORMERS } />
CharacterLimitPlugin
Limits the number of characters in the editor.
Maximum number of characters allowed.
renderer
(props: { characters: number; maxLength: number; remainingCharacters: number }) => JSX.Element
Custom renderer for displaying character count.
import { CharacterLimitPlugin } from '@lexical/react/LexicalCharacterLimitPlugin' ;
< CharacterLimitPlugin
maxLength = { 500 }
renderer = { ({ remainingCharacters }) => (
< div className = "character-count" >
{ remainingCharacters } characters remaining
</ div >
) }
/>
Utility Plugins
EditorRefPlugin
Provides access to the editor instance outside the composer tree.
import { EditorRefPlugin } from '@lexical/react/LexicalEditorRefPlugin' ;
import { useRef } from 'react' ;
import { LexicalEditor } from 'lexical' ;
function App () {
const editorRef = useRef < LexicalEditor >( null );
const handleExternalAction = () => {
if ( editorRef . current ) {
editorRef . current . update (() => {
// Perform actions
});
}
};
return (
<>
< button onClick = { handleExternalAction } > External Button </ button >
< LexicalComposer initialConfig = { config } >
< EditorRefPlugin editorRef = { editorRef } />
{ /* other plugins */ }
</ LexicalComposer >
</>
);
}
ClearEditorPlugin
Provides a command to clear all editor content.
import { ClearEditorPlugin } from '@lexical/react/LexicalClearEditorPlugin' ;
import { CLEAR_EDITOR_COMMAND } from 'lexical' ;
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' ;
function ClearButton () {
const [ editor ] = useLexicalComposerContext ();
return (
< button onClick = { () => editor . dispatchCommand ( CLEAR_EDITOR_COMMAND , undefined ) } >
Clear
</ button >
);
}
// In your editor:
< ClearEditorPlugin />
TreeViewPlugin
Displays a debug view of the editor’s node tree.
import { TreeViewPlugin } from '@lexical/react/LexicalTreeView' ;
< TreeViewPlugin />
Creating Custom Plugins
Basic Plugin Pattern
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' ;
import { useEffect } from 'react' ;
import { COMMAND_PRIORITY_LOW } from 'lexical' ;
function CustomPlugin () {
const [ editor ] = useLexicalComposerContext ();
useEffect (() => {
// Register update listener
const removeUpdateListener = editor . registerUpdateListener (
({ editorState }) => {
editorState . read (() => {
// Read editor state
});
}
);
// Register command
const removeCommand = editor . registerCommand (
CUSTOM_COMMAND ,
( payload ) => {
// Handle command
return false ; // or true to stop propagation
},
COMMAND_PRIORITY_LOW
);
// Cleanup
return () => {
removeUpdateListener ();
removeCommand ();
};
}, [ editor ]);
return null ;
}
Plugin with UI
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' ;
import { useState , useEffect } from 'react' ;
import { $getRoot } from 'lexical' ;
function WordCountPlugin () {
const [ editor ] = useLexicalComposerContext ();
const [ wordCount , setWordCount ] = useState ( 0 );
useEffect (() => {
return editor . registerUpdateListener (({ editorState }) => {
editorState . read (() => {
const root = $getRoot ();
const text = root . getTextContent ();
const words = text . split ( / \s + / ). filter ( Boolean );
setWordCount ( words . length );
});
});
}, [ editor ]);
return (
< div className = "word-count" >
Word count: { wordCount }
</ div >
);
}
Plugin with Commands
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' ;
import { useEffect } from 'react' ;
import { createCommand , COMMAND_PRIORITY_NORMAL } from 'lexical' ;
export const INSERT_EMOJI_COMMAND = createCommand < string >( 'INSERT_EMOJI' );
function EmojiPlugin () {
const [ editor ] = useLexicalComposerContext ();
useEffect (() => {
return editor . registerCommand (
INSERT_EMOJI_COMMAND ,
( emoji ) => {
editor . update (() => {
const selection = $getSelection ();
if ( $isRangeSelection ( selection )) {
selection . insertText ( emoji );
}
});
return true ;
},
COMMAND_PRIORITY_NORMAL
);
}, [ editor ]);
return null ;
}
// Usage in another component:
function EmojiButton ({ emoji } : { emoji : string }) {
const [ editor ] = useLexicalComposerContext ();
return (
< button onClick = { () => editor . dispatchCommand ( INSERT_EMOJI_COMMAND , emoji ) } >
{ emoji }
</ button >
);
}
Plugin Best Practices
Single Responsibility Each plugin should focus on one feature. Split complex functionality into multiple plugins.
Cleanup Functions Always return cleanup functions from useEffect to unregister listeners and prevent memory leaks.
Command Priorities Use appropriate command priorities. Higher priority handlers run first and can stop propagation.
State Management Use React state for UI concerns. Store editor data in EditorState, not React state.
Never mutate the editor state outside of editor.update() or editor.read() callbacks. This will cause inconsistencies and errors.
Use mergeRegister() from @lexical/utils to combine multiple cleanup functions into one. import { mergeRegister } from '@lexical/utils' ;
useEffect (() => {
return mergeRegister (
editor . registerUpdateListener ( ... ),
editor . registerCommand ( ... ),
editor . registerNodeTransform ( ... )
);
}, [ editor ]);
React Hooks Explore hooks for accessing editor state and functionality
Commands Learn about the command system in Lexical
Node Transforms Understand how to use node transforms
Listeners Deep dive into editor listeners