Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Overview
    • Why This Fork
    • Examples
    • Installing Plite
    • Adding Event Handlers
    • Defining Custom Elements
    • Applying Custom Formatting
    • Executing Commands
    • Saving to a Database
    • Canonical Change Substrate
    • Improving Performance
    • Interfaces
    • Nodes
    • Locations
    • Transforms
    • Document Changes
    • Commands
    • Editor
    • Plugins
    • Rendering
    • Serializing
    • Normalizing
    • Using TypeScript
    • Roots
    • Document State
    • Editing Behavior
    • Selection And DOM
    • Clipboard And Paste
    • Decorations, annotations, and widgets
    • Schema
    • 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
    • Plite DOM
    • History Editor API
    • History Plugin Setup
    • History
    • Plite History
    • Plite Hyperscript
    • Plite PaginationExperimental
    • Annotations
    • DOM Coverage Boundaries
    • Editable Component
    • External Text Views
    • Plite React Event Handling
    • Virtualized Rendering
    • Plite React Hooks
    • React Editor Setup
    • React Editor
    • Plite React
    • Plite Component
    • Plite Yjs
    • Plite
    • Migration
    • Contributing
    • 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 "plitejs";
 
function insertParagraph(editor: Editor) {
  editor.update.nodes.insert({ type: "paragraph", children: [{ text: "" }] });
}
import type { Editor } from "plitejs";
 
function insertParagraph(editor: Editor) {
  editor.update.nodes.insert({ type: "paragraph", children: [{ text: "" }] });
}

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

import { defineCommand } from "plitejs";
 
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 "plitejs";
 
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 plugin policy, then applies the handled spec in one update. It returns true when the spec landed, false when no handler applied, and undefined when the editor refused the update.

Callbacks passed to state.transaction(...) and state.transaction.extend(base, callback) must finish synchronously. Returning a Promise or other thenable throws and discards the spec draft. Complete async work before building a spec or starting an update.

Register ordinary fallback policy with the plugin 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 { definePlugin, editorCommands } from "plitejs";
 
const noEmptyText = definePlugin("no-empty-text", {
  commands: ({ handle }) => [
    handle(editorCommands.insertText, ({ input, state }) =>
      input.text.length === 0
        ? state.transaction(() => {})
        : false
    ),
  ],
});
import { definePlugin, editorCommands } from "plitejs";
 
const noEmptyText = definePlugin("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. Plugin configuration determines handler order.

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

import { ElementApi, TextApi } from "plitejs";
 
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 "plitejs";
 
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 });
});