The LexicalEditor is the central orchestrator of Lexical. It manages the editor state, configuration, listeners, commands, and DOM reconciliation. Every Lexical editor begins with a call to createEditor().
Creating an Editor
The createEditor() function is the primary way to initialize a Lexical editor:
import { createEditor } from 'lexical' ;
const editor = createEditor ({
namespace: 'MyEditor' ,
theme: {
// Theme classes for styling
paragraph: 'editor-paragraph' ,
text: {
bold: 'editor-text-bold' ,
italic: 'editor-text-italic' ,
},
},
nodes: [
// Custom node classes
HeadingNode ,
QuoteNode ,
],
onError : ( error ) => {
console . error ( error );
},
});
Configuration Options
The CreateEditorArgs interface defines all available configuration options:
type CreateEditorArgs = {
/** Disable event listeners (useful for headless environments) */
disableEvents ?: boolean ;
/** Initial editor state to load */
editorState ?: EditorState ;
/** Unique namespace for the editor (defaults to auto-generated) */
namespace ?: string ;
/** Array of node classes to register */
nodes ?: ReadonlyArray < LexicalNodeConfig >;
/** Error handler callback */
onError ?: ErrorHandler ;
/** Parent editor for nested editor scenarios */
parentEditor ?: LexicalEditor ;
/** Whether the editor is editable (default: true) */
editable ?: boolean ;
/** Theme configuration for applying CSS classes */
theme ?: EditorThemeClasses ;
/** HTML import/export configuration */
html ?: HTMLConfig ;
};
Attaching to the DOM
After creating the editor, attach it to a DOM element:
const contentEditableElement = document . getElementById ( 'editor' );
editor . setRootElement ( contentEditableElement );
The root element should have the contenteditable attribute. Lexical will manage all DOM interactions through this element.
Core Editor Methods
State Management
getEditorState()
Returns the current immutable EditorState:
const currentState = editor . getEditorState ();
setEditorState()
Imperatively sets a new editor state:
const newState = editor . parseEditorState ( jsonState );
editor . setEditorState ( newState );
Direct state mutation via setEditorState() triggers reconciliation. For programmatic changes, prefer using editor.update().
update()
The primary way to make changes to the editor state:
editor . update (() => {
const root = $getRoot ();
const paragraph = $createParagraphNode ();
const text = $createTextNode ( 'Hello, world!' );
paragraph . append ( text );
root . append ( paragraph );
});
See Updates for more details.
read()
Read from the editor state without making changes:
const textContent = editor . read (() => {
const root = $getRoot ();
return root . getTextContent ();
});
Listeners
registerUpdateListener()
Listen to all editor updates:
const removeListener = editor . registerUpdateListener (
({ editorState , dirtyElements , dirtyLeaves , tags }) => {
// React to changes
console . log ( 'Editor updated' );
editorState . read (() => {
const root = $getRoot ();
console . log ( root . getTextContent ());
});
}
);
// Cleanup
removeListener ();
The UpdateListenerPayload includes:
interface UpdateListenerPayload {
/** Map of dirty ElementNode keys to intentional mutation flags */
dirtyElements : Map < NodeKey , boolean >;
/** Set of dirty leaf node keys */
dirtyLeaves : Set < NodeKey >;
/** New editor state after update */
editorState : EditorState ;
/** Map of node mutations (created/updated/destroyed) */
mutatedNodes : null | MutatedNodes ;
/** Set of normalized (merged) TextNode keys */
normalizedNodes : Set < NodeKey >;
/** Previous editor state */
prevEditorState : EditorState ;
/** Tags associated with this update */
tags : Set < string >;
}
registerMutationListener()
Listen to specific node type mutations:
const removeMutationListener = editor . registerMutationListener (
ImageNode ,
( mutatedNodes , { updateTags }) => {
mutatedNodes . forEach (( mutation , nodeKey ) => {
if ( mutation === 'created' ) {
const element = editor . getElementByKey ( nodeKey );
// Attach event listeners to the DOM element
}
});
}
);
By default (as of v0.21.0), mutation listeners are called immediately with existing nodes. Use skipInitialization: true to opt out of this behavior.
registerCommand()
Register command handlers with priority levels:
import { FORMAT_TEXT_COMMAND , COMMAND_PRIORITY_NORMAL } from 'lexical' ;
const removeCommand = editor . registerCommand (
FORMAT_TEXT_COMMAND ,
( payload ) => {
// Handle the command
console . log ( 'Format command:' , payload );
// Return true to stop propagation
return false ;
},
COMMAND_PRIORITY_NORMAL
);
See Commands for detailed command system documentation.
Other Listeners
// Editable state changes
editor . registerEditableListener (( isEditable ) => {
console . log ( 'Editable:' , isEditable );
});
// Root element changes
editor . registerRootListener (( rootElement , prevRootElement ) => {
if ( rootElement ) {
// Editor mounted
}
});
// Text content changes (debounced)
editor . registerTextContentListener (( text ) => {
console . log ( 'Text:' , text );
});
// Decorator changes (for DecoratorNodes)
editor . registerDecoratorListener (( decorators ) => {
// Render decorators in your framework
});
Commands
dispatchCommand()
Dispatch a command to all registered handlers:
import { FORMAT_TEXT_COMMAND } from 'lexical' ;
editor . dispatchCommand ( FORMAT_TEXT_COMMAND , 'bold' );
Commands are dispatched in priority order (highest first), and handlers can stop propagation by returning true.
Focus Management
focus()
Focus the editor programmatically:
editor . focus (() => {
// Optional callback after focus
}, {
defaultSelection: 'rootEnd' // or 'rootStart'
});
blur()
Remove focus from the editor:
Node Registration
hasNode() / hasNodes()
Check if node types are registered:
if ( editor . hasNode ( HeadingNode )) {
// HeadingNode is available
}
if ( editor . hasNodes ([ HeadingNode , QuoteNode ])) {
// Both nodes are available
}
Serialization
toJSON()
Serialize the entire editor to JSON:
const json = editor . toJSON ();
// { editorState: { root: { ... } } }
parseEditorState()
Parse JSON back into an EditorState:
const state = editor . parseEditorState ( jsonString );
editor . setEditorState ( state );
Editor State
Editable Mode
// Check if editable
if ( editor . isEditable ()) {
// Editor accepts user input
}
// Toggle editable
editor . setEditable ( false ); // Read-only
editor . setEditable ( true ); // Editable
Composition State
Check if the editor is in composition mode (IME input):
if ( editor . isComposing ()) {
// User is composing text via IME
}
Advanced Features
Register transforms that run when nodes of a specific type are marked dirty:
const removeTransform = editor . registerNodeTransform (
TextNode ,
( node ) => {
// Transform logic
if ( node . getTextContent (). includes ( 'TODO:' )) {
node . setFormat ( 'bold' );
}
}
);
See Transforms for more details.
Getting DOM Elements
getElementByKey()
Get the DOM element for a node key:
const element = editor . getElementByKey ( nodeKey );
if ( element ) {
// Attach event listeners, etc.
}
getRootElement()
Get the current root contenteditable element:
const rootElement = editor . getRootElement ();
Decorators
Get all current decorators (for DecoratorNode rendering):
const decorators = editor . getDecorators ();
Best Practices
Always use update() or read() for state access
Never access node properties outside of an update() or read() callback. The editor state is only accessible within these contexts. // ❌ Bad
const root = $getRoot (); // Will throw error
// ✅ Good
editor . read (() => {
const root = $getRoot ();
});
Always store and call the cleanup functions returned by register* methods: const cleanup = editor . registerUpdateListener ( listener );
// Later...
cleanup ();
Use appropriate command priorities
Understand the command priority system:
COMMAND_PRIORITY_EDITOR (0) - Framework/editor level
COMMAND_PRIORITY_LOW (1) - Low priority plugins
COMMAND_PRIORITY_NORMAL (2) - Most plugins
COMMAND_PRIORITY_HIGH (3) - Important plugins
COMMAND_PRIORITY_CRITICAL (4) - Critical overrides
Higher priorities run first and can intercept commands.
Understand the update batching
Multiple synchronous update() calls are batched together: editor . update (() => { /* change 1 */ });
editor . update (() => { /* change 2 */ });
// Both changes reconciled in a single pass
Type Signatures
LexicalEditor Class
Key properties (internal, for reference):
class LexicalEditor {
/** Current editor state */
_editorState : EditorState ;
/** Editor configuration */
_config : EditorConfig ;
/** Registered node types */
_nodes : RegisteredNodes ;
/** Root DOM element */
_rootElement : null | HTMLElement ;
/** Whether editor is editable */
_editable : boolean ;
// ... many other internal properties
}
Editor State - Understanding the immutable state model
Updates - How to make changes to the editor
Commands - The command system in depth
Nodes - Working with the node system