Plite changes the document inside editor.update(...). The callback receives a
transaction object named tx. Use tx for writes and for reads that belong to
the command you are running.
For one write, use the direct update method:
editor.update.text.insert("some words");editor.update.text.insert("some words");Use the callback form when a command composes multiple writes, reads transaction state, adjusts update tags, or registers post-commit work.
Transaction Groups
editor.update((tx) => {
tx.nodes.unwrap({
at: [],
match: (node) =>
ElementApi.isElement(node) &&
node.children.every((child) => ElementApi.isElement(child)),
mode: "all",
});
});editor.update((tx) => {
tx.nodes.unwrap({
at: [],
match: (node) =>
ElementApi.isElement(node) &&
node.children.every((child) => ElementApi.isElement(child)),
mode: "all",
});
});The core transaction groups are:
tx.nodesfor inserting, removing, moving, wrapping, unwrapping, and setting nodestx.fragmentfor reading, inserting, and deleting fragmentstx.textfor inserting and deleting texttx.breakfor inserting block and soft breakstx.selectionfor changing the model selectiontx.marksfor changing text markstx.valueandtx.rootsfor whole-root or whole-document replacementtx.setFieldandtx.statePatchesfor persistent meta fieldstx.normalizefor an explicit normalization barriertx.operationsfor replaying operation batches
Commands should stay inside one update when the changes belong to one user action. Plite records the operations and publishes one commit when the update finishes.
See Transforms API for method options and the complete transaction reference.
Value Replacement
Use editor.update.value.replace(...) for one-shot imports, reset buttons, and
controlled external replacements.
editor.update({ history: "skip" }).value.replace({
children: [{ type: "paragraph", children: [{ text: "Imported" }] }],
selection: "start",
});editor.update({ history: "skip" }).value.replace({
children: [{ type: "paragraph", children: [{ text: "Imported" }] }],
selection: "start",
});Use the callback form when the same replacement also edits extra roots or persistent meta fields.
Selection
Use tx.selection to set, clear, collapse, or move the selection.
editor.update((tx) => {
tx.selection.set({
anchor: { path: [0, 0], offset: 0 },
focus: { path: [1, 0], offset: 2 },
});
});editor.update((tx) => {
tx.selection.set({
anchor: { path: [0, 0], offset: 0 },
focus: { path: [1, 0], offset: 2 },
});
});Move the cursor backward by three words:
editor.update((tx) => {
tx.selection.move({
distance: 3,
reverse: true,
unit: "word",
});
});editor.update((tx) => {
tx.selection.move({
distance: 3,
reverse: true,
unit: "word",
});
});Read selection with editor.read(...) when you are outside an update:
const selection = editor.read((state) => state.selection());const selection = editor.read((state) => state.selection());Text
Insert text at the current transaction target:
editor.update((tx) => {
tx.text.insert("some words");
});editor.update((tx) => {
tx.text.insert("some words");
});Insert text at an explicit point:
editor.update((tx) => {
tx.text.insert("some words", {
at: { path: [0, 0], offset: 3 },
});
});editor.update((tx) => {
tx.text.insert("some words", {
at: { path: [0, 0], offset: 3 },
});
});Delete a range:
editor.update((tx) => {
tx.text.delete({
at: {
anchor: { path: [0, 0], offset: 0 },
focus: { path: [1, 0], offset: 2 },
},
});
});editor.update((tx) => {
tx.text.delete({
at: {
anchor: { path: [0, 0], offset: 0 },
focus: { path: [1, 0], offset: 2 },
},
});
});Nodes
Insert a text node at an explicit path:
editor.update((tx) => {
tx.nodes.insert(
{
text: "A new string of text.",
},
{
at: [0, 1],
}
);
});editor.update((tx) => {
tx.nodes.insert(
{
text: "A new string of text.",
},
{
at: [0, 1],
}
);
});Move a node:
editor.update((tx) => {
tx.nodes.move({
at: [0, 0],
to: [0, 1],
});
});editor.update((tx) => {
tx.nodes.move({
at: [0, 0],
to: [0, 1],
});
});Set a property on matching text nodes:
editor.update((tx) => {
tx.nodes.set(
{ bold: true },
{
at: [],
match: (node) => TextApi.isText(node) && node.italic !== true,
}
);
});editor.update((tx) => {
tx.nodes.set(
{ bold: true },
{
at: [],
match: (node) => TextApi.isText(node) && node.italic !== true,
}
);
});When you already have the exact node, pass it as at. The node type narrows the
properties accepted by set.
editor.update.nodes.set({ icon: "🔥" }, { at: calloutElement });editor.update.nodes.set({ icon: "🔥" }, { at: calloutElement });Marks
Use tx.marks for text formatting commands.
editor.update.marks.toggle("bold");editor.update.marks.toggle("bold");You can read marks inside the same command:
editor.update((tx) => {
const marks = tx.marks();
if (marks?.code) {
tx.marks.remove("code");
} else {
tx.marks.add("code", true);
}
});editor.update((tx) => {
const marks = tx.marks();
if (marks?.code) {
tx.marks.remove("code");
} else {
tx.marks.add("code", true);
}
});Operations
Operation replay is still a transaction. Import remote or stored operations
through tx.operations.replay(...) so replay follows the same commit,
normalization, history, and subscription boundary as local commands.
editor.update({ tags: "remote-import" }, (tx) => {
tx.operations.replay(remoteOperations);
});editor.update({ tags: "remote-import" }, (tx) => {
tx.operations.replay(remoteOperations);
});The at Option
When at is omitted, selection-sensitive methods use the transaction target.
When at is provided, Plite uses that exact location and does not import or
refresh browser selection.
editor.update((tx) => {
tx.text.insert("some words");
});
editor.update((tx) => {
tx.text.insert("some words", {
at: { path: [0, 0], offset: 3 },
});
});editor.update((tx) => {
tx.text.insert("some words");
});
editor.update((tx) => {
tx.text.insert("some words", {
at: { path: [0, 0], offset: 3 },
});
});at can be a Path, Point, Range, text node, or element node. Node targets
resolve to their current path in the active editor root.
editor.update((tx) => {
tx.text.insert("some words", {
at: {
anchor: { path: [0, 0], offset: 0 },
focus: { path: [0, 0], offset: 3 },
},
});
});editor.update((tx) => {
tx.text.insert("some words", {
at: {
anchor: { path: [0, 0], offset: 0 },
focus: { path: [0, 0], offset: 3 },
},
});
});The match Option
Node methods accept a predicate or a shallow property object to restrict which nodes are changed.
editor.update((tx) => {
tx.nodes.move({
at: [2],
match: (node, path) => path.length === 2,
to: [5],
});
});editor.update((tx) => {
tx.nodes.move({
at: [2],
match: (node, path) => path.length === 2,
to: [5],
});
});The match function can examine the node, the path, or surrounding structure
through transaction read helpers.
Use property objects for exact fields and one-of sets.
const callout = editor.read.nodes.find({
match: { type: "callout" },
});
const tableNode = editor.read.nodes.find({
match: { type: ["table", "table_cell"] },
});const callout = editor.read.nodes.find({
match: { type: "callout" },
});
const tableNode = editor.read.nodes.find({
match: { type: ["table", "table_cell"] },
});Property matching is shallow. Scalar values use exact equality; array values mean one-of. Use a predicate for computed policies such as block, text, or empty checks, and whenever TypeScript should narrow the matched node.
Normalization
Keep structural commands in one editor.update(...) call. Plite normalizes the
result before publishing the commit.
editor.update((tx) => {
tx.nodes.unwrap({ match: isList });
tx.nodes.set({ type: "list-item" });
tx.nodes.wrap({ type: "bulleted-list", children: [] });
});editor.update((tx) => {
tx.nodes.unwrap({ match: isList });
tx.nodes.set({ type: "list-item" });
tx.nodes.wrap({ type: "bulleted-list", children: [] });
});Runtime internals may use lower-level helpers, but public document changes and operation replay stay inside the transaction boundary.