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(...).
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 });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.
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.