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 "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.