Skip to main content
Commands are the primary mechanism for communicating actions and events in Lexical. They provide a decoupled way to trigger behaviors across your editor.

What are Commands?

Commands in Lexical are:
  • Type-safe event dispatchers
  • Priority-based with propagation control
  • Decoupled from implementation
  • Used for keyboard events, formatting, custom actions, and more

Creating Commands

Create a command using createCommand():
import { createCommand, LexicalCommand } from 'lexical';

export const INSERT_IMAGE_COMMAND: LexicalCommand<{
  src: string;
  altText: string;
}> = createCommand('INSERT_IMAGE_COMMAND');
The generic type parameter defines the command’s payload type:
// Command with no payload
const CLEAR_EDITOR_COMMAND: LexicalCommand<void> = 
  createCommand('CLEAR_EDITOR');

// Command with string payload
const INSERT_TEXT_COMMAND: LexicalCommand<string> = 
  createCommand('INSERT_TEXT');

// Command with object payload
const INSERT_TABLE_COMMAND: LexicalCommand<{
  rows: number;
  columns: number;
}> = createCommand('INSERT_TABLE');

// Command with event payload
const PASTE_COMMAND: LexicalCommand<ClipboardEvent> = 
  createCommand('PASTE_COMMAND');

Dispatching Commands

Dispatch commands using editor.dispatchCommand():
import { $getSelection, $isRangeSelection } from 'lexical';

// Dispatch from outside editor context
editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
  src: 'https://example.com/image.jpg',
  altText: 'Description',
});

// Dispatch from inside editor.update()
editor.update(() => {
  const selection = $getSelection();
  if ($isRangeSelection(selection)) {
    editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
  }
});
In React components:
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { INSERT_IMAGE_COMMAND } from './commands';

function ImageButton() {
  const [editor] = useLexicalComposerContext();

  const handleClick = () => {
    editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
      src: '/placeholder.jpg',
      altText: 'Placeholder',
    });
  };

  return <button onClick={handleClick}>Insert Image</button>;
}

Registering Command Handlers

Register handlers with editor.registerCommand():
import { COMMAND_PRIORITY_EDITOR } from 'lexical';

const removeCommand = editor.registerCommand(
  INSERT_IMAGE_COMMAND,
  (payload) => {
    const { src, altText } = payload;
    
    editor.update(() => {
      const selection = $getSelection();
      if ($isRangeSelection(selection)) {
        const imageNode = $createImageNode({ src, altText });
        selection.insertNodes([imageNode]);
      }
    });
    
    return true; // Stop propagation
  },
  COMMAND_PRIORITY_EDITOR,
);

// Always cleanup
removeCommand();
In React plugins:
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
import { COMMAND_PRIORITY_EDITOR } from 'lexical';

function ImagePlugin(): null {
  const [editor] = useLexicalComposerContext();

  useEffect(() => {
    return editor.registerCommand(
      INSERT_IMAGE_COMMAND,
      (payload) => {
        // Handle command
        return true;
      },
      COMMAND_PRIORITY_EDITOR,
    );
  }, [editor]);

  return null;
}

Command Priority

Lexical supports 5 priority levels (highest to lowest):
import {
  COMMAND_PRIORITY_CRITICAL,  // 4 - Critical system commands
  COMMAND_PRIORITY_HIGH,      // 3 - Important plugins
  COMMAND_PRIORITY_NORMAL,    // 2 - Standard plugins
  COMMAND_PRIORITY_LOW,       // 1 - Lower priority handlers
  COMMAND_PRIORITY_EDITOR,    // 0 - Editor's default handlers
} from 'lexical';
Higher priority handlers execute first:
// This runs first (priority 3)
editor.registerCommand(
  KEY_ENTER_COMMAND,
  (event) => {
    // Handle enter in code blocks
    return true; // Stop propagation
  },
  COMMAND_PRIORITY_HIGH,
);

// This only runs if above handler returns false (priority 0)
editor.registerCommand(
  KEY_ENTER_COMMAND,
  (event) => {
    // Default enter behavior
    return true;
  },
  COMMAND_PRIORITY_EDITOR,
);

Command Propagation

Handlers can control propagation by their return value:
  • true - Stop propagation (command handled)
  • false - Continue to next handler
editor.registerCommand(
  MY_COMMAND,
  (payload) => {
    if (shouldHandle(payload)) {
      // Handle the command
      return true; // Stop propagation
    }
    return false; // Let other handlers try
  },
  COMMAND_PRIORITY_NORMAL,
);

Built-in Commands

Lexical provides many built-in commands:

Text Formatting

import {
  FORMAT_TEXT_COMMAND,
  FORMAT_ELEMENT_COMMAND,
} from 'lexical';

// Format selected text
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic');
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline');

// Format element alignment
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center');
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right');

Text Insertion

import {
  INSERT_PARAGRAPH_COMMAND,
  INSERT_LINE_BREAK_COMMAND,
  CONTROLLED_TEXT_INSERTION_COMMAND,
} from 'lexical';

editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);
editor.dispatchCommand(INSERT_LINE_BREAK_COMMAND, false);
editor.dispatchCommand(CONTROLLED_TEXT_INSERTION_COMMAND, 'Hello');

Deletion

import {
  DELETE_CHARACTER_COMMAND,
  DELETE_WORD_COMMAND,
  DELETE_LINE_COMMAND,
  REMOVE_TEXT_COMMAND,
} from 'lexical';

// Delete backward (true) or forward (false)
editor.dispatchCommand(DELETE_CHARACTER_COMMAND, true);
editor.dispatchCommand(DELETE_WORD_COMMAND, true);
editor.dispatchCommand(DELETE_LINE_COMMAND, false);

Keyboard Events

import {
  KEY_ENTER_COMMAND,
  KEY_ARROW_LEFT_COMMAND,
  KEY_ARROW_RIGHT_COMMAND,
  KEY_BACKSPACE_COMMAND,
  KEY_DELETE_COMMAND,
  KEY_TAB_COMMAND,
} from 'lexical';

editor.registerCommand(
  KEY_ENTER_COMMAND,
  (event: KeyboardEvent) => {
    // Handle enter key
    return false;
  },
  COMMAND_PRIORITY_NORMAL,
);

Clipboard

import {
  COPY_COMMAND,
  CUT_COMMAND,
  PASTE_COMMAND,
} from 'lexical';

editor.registerCommand(
  PASTE_COMMAND,
  (event: ClipboardEvent) => {
    // Custom paste handling
    return false;
  },
  COMMAND_PRIORITY_NORMAL,
);

History

import {
  UNDO_COMMAND,
  REDO_COMMAND,
} from 'lexical';

editor.dispatchCommand(UNDO_COMMAND, undefined);
editor.dispatchCommand(REDO_COMMAND, undefined);

Selection

import {
  SELECT_ALL_COMMAND,
  SELECTION_CHANGE_COMMAND,
} from 'lexical';

editor.dispatchCommand(SELECT_ALL_COMMAND, undefined);

editor.registerCommand(
  SELECTION_CHANGE_COMMAND,
  () => {
    // Handle selection changes
    return false;
  },
  COMMAND_PRIORITY_NORMAL,
);
See packages/lexical/src/LexicalCommands.ts for all built-in commands.

Custom Command Examples

Simple Command

export const CLEAR_EDITOR_COMMAND: LexicalCommand<void> = 
  createCommand('CLEAR_EDITOR');

// Handler
editor.registerCommand(
  CLEAR_EDITOR_COMMAND,
  () => {
    editor.update(() => {
      const root = $getRoot();
      root.clear();
      root.append($createParagraphNode());
    });
    return true;
  },
  COMMAND_PRIORITY_EDITOR,
);

// Dispatch
editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);

Command with Payload

export const INSERT_EMOJI_COMMAND: LexicalCommand<string> = 
  createCommand('INSERT_EMOJI');

// Handler
editor.registerCommand(
  INSERT_EMOJI_COMMAND,
  (emoji: string) => {
    editor.update(() => {
      const selection = $getSelection();
      if ($isRangeSelection(selection)) {
        const emojiNode = $createEmojiNode('emoji', emoji);
        selection.insertNodes([emojiNode]);
      }
    });
    return true;
  },
  COMMAND_PRIORITY_EDITOR,
);

// Dispatch
editor.dispatchCommand(INSERT_EMOJI_COMMAND, '👍');

Conditional Handler

editor.registerCommand(
  KEY_ENTER_COMMAND,
  (event: KeyboardEvent) => {
    const selection = $getSelection();
    if (!$isRangeSelection(selection)) {
      return false;
    }

    const node = selection.anchor.getNode();
    const codeBlock = $findMatchingParent(
      node,
      (n) => $isCodeNode(n),
    );

    if (codeBlock) {
      // Handle enter in code blocks
      event.preventDefault();
      selection.insertText('\n');
      return true;
    }

    return false; // Let default handler run
  },
  COMMAND_PRIORITY_HIGH,
);

Best Practices

  • Type Safety: Always define payload types for commands
  • Return Values: Return true to stop propagation, false to continue
  • Priority: Use appropriate priority levels for your use case
  • Cleanup: Always clean up command listeners
  • Updates: Wrap mutations in editor.update() when handling commands
  • Naming: Use descriptive, ACTION_NOUN format (e.g., INSERT_IMAGE_COMMAND)
  • Export: Export commands from a central location for reusability

See Also

Build docs developers (and LLMs) love