From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
UNPUBLISHED
  • Overview
  • Why This Fork
  • Examples
Walkthroughs
  • Installing Plite
  • Adding Event Handlers
  • Defining Custom Elements
  • Applying Custom Formatting
  • Executing Commands
  • Saving to a Database
  • Canonical Change Substrate
  • Improving Performance
Concepts
  • Interfaces
  • Nodes
  • Locations
  • Transforms
  • Document Changes
  • Commands
  • Editor
  • Extensions
  • Rendering
  • Serializing
  • Normalizing
  • Using TypeScript
  • Roots
  • Document State
  • Editing Behavior
  • Selection And DOM
  • Clipboard And Paste
  • Projection And Overlays
  • Schema
API
  • Anchor API
  • Location API
  • Path API
  • PointEntry API
  • Point API
  • Range API
  • Selection API
  • Location Types APIs
  • Span API
  • Editor
  • Element API
  • NodeEntry API
  • Node API
  • Node Types APIs
  • Text API
  • Debug Value Scrubbing
  • Transforms API
Libraries
  • Plite DOM
  • History Editor API
  • History Extension Setup
  • History
  • Plite History
  • Plite Hyperscript
  • Plite Layout
  • Annotations
  • DOM Coverage Boundaries
  • Editable Component
  • Plite React Event Handling
  • Virtualized Rendering
  • Plite React Hooks
  • React Editor Setup
  • React Editor
  • Plite React
  • Plite Component
  • Plite Yjs
  • Plite
General
  • Migration
  • Contributing
  • Docs Proof Map
  • FAQ
  • Resources

Commands

PreviousNext

Group user and product intent into reusable functions that run Plite transactions.

Commands are high-level actions that represent user or product intent. In Plite, command helpers are ordinary functions that run related transaction writes inside editor.update(...).

Command Shape

For example, here are some of the built-in commands:

editor.update.text.insert("A new string of text to be inserted.");
editor.update.text.delete({ reverse: true, unit: "word" });
editor.update.nodes.split({ always: true });
Document ChangesEditor

On This Page

Command ShapeCustom Commands
Build your editor
Production-ready AI template and reusable components.
Get all-access
editor.update.text.insert("A new string of text to be inserted."); editor.update.text.delete({ reverse: true, unit: "word" }); editor.update.nodes.split({ always: true });

Define custom commands for your product domain, such as formatQuote, insertImage, or toggleBold.

Commands usually act on the current selection. Pass an explicit at location only when the command is intentionally targeting another part of the document.

Plite composes transaction writes into one canonical DocumentChange during the update. That is the boundary used by history, collaboration, and tests.

Custom Commands

When defining custom commands, pass the editor into a function and keep the writes grouped:

import type { Editor } from "@platejs/plite";
 
function insertParagraph(editor: Editor) {
  editor.update.nodes.insert({ type: "paragraph", children: [{ text: "" }] });
}
import type { Editor } from "@platejs/plite";
 
function insertParagraph(editor: Editor) {
  editor.update.nodes.insert({ type: "paragraph", children: [{ text: "" }] });
}

Use defineCommand when a command must be evaluated headlessly, intercepted by extensions, or inspected before publication. Its pure builder returns false or an immutable transaction spec.

import { defineCommand } from "@platejs/plite";
 
type InsertTextInput = {
  text: string;
};
 
const insertText = defineCommand<InsertTextInput>("text.insert", {
  build: ({ input, state }) =>
    state.transaction((tx) => {
      tx.text.insert(input.text);
    }),
});
 
editor.update.command(insertText, { text: "Hello" });
import { defineCommand } from "@platejs/plite";
 
type InsertTextInput = {
  text: string;
};
 
const insertText = defineCommand<InsertTextInput>("text.insert", {
  build: ({ input, state }) =>
    state.transaction((tx) => {
      tx.text.insert(input.text);
    }),
});
 
editor.update.command(insertText, { text: "Hello" });

state.transaction(...) builds a frozen TransactionSpec without publishing a commit. editor.update.command(...) runs extension policy, then applies the handled spec in one update. insertText.build(state, input) evaluates only the descriptor default and does not run installed handlers.

Register ordinary fallback policy with the extension command factory. The descriptor stays first so its identity and input type remain linked to the handler. Return false to let the next handler or descriptor default run:

import { defineExtension, editorCommands } from "@platejs/plite";
 
const noEmptyText = defineExtension("no-empty-text", {
  commands: ({ handle }) => [
    handle(editorCommands.insertText, ({ input, state }) =>
      input.text.length === 0
        ? state.transaction(() => {})
        : false
    ),
  ],
});
import { defineExtension, editorCommands } from "@platejs/plite";
 
const noEmptyText = defineExtension("no-empty-text", {
  commands: ({ handle }) => [
    handle(editorCommands.insertText, ({ input, state }) =>
      input.text.length === 0
        ? state.transaction(() => {})
        : false
    ),
  ],
});

Use the factory's around(descriptor, handler) only when policy must rewrite downstream input or compose a prefix with downstream behavior. Its context adds next; next.after(prefix) runs downstream against the state produced by that prefix. Extension configuration determines handler order.

When writing your own commands, compose transaction methods inside one update:

import { ElementApi, TextApi } from "@platejs/plite";
 
editor.update((tx) => {
  tx.nodes.set(
    { bold: true },
    {
      at: range,
      match: (node) => TextApi.isText(node),
      split: true,
    }
  );
 
  tx.nodes.wrap(
    { type: "quote", children: [] },
    {
      at: point,
      match: (node) => ElementApi.isElement(node) && tx.schema.isBlock(node),
      mode: "lowest",
    }
  );
 
  tx.text.insert("A new string of text.", { at: path });
});
import { ElementApi, TextApi } from "@platejs/plite";
 
editor.update((tx) => {
  tx.nodes.set(
    { bold: true },
    {
      at: range,
      match: (node) => TextApi.isText(node),
      split: true,
    }
  );
 
  tx.nodes.wrap(
    { type: "quote", children: [] },
    {
      at: point,
      match: (node) => ElementApi.isElement(node) && tx.schema.isBlock(node),





Transaction methods are designed to be composed together. Keep related writes in the same editor.update(...) so selection, canonical changes, history, and React rendering share one commit.

mode: "lowest",
}
);
tx.text.insert("A new string of text.", { at: path });
});