Transforms API

PreviousNext

Transaction transform groups for node, text, selection, mark, root, state, and operation writes.

Transforms are transaction helpers used inside editor.update(...).

Basic Usage

editor.update((tx) => {
  tx.text.insert("Hello");
});
editor.update((tx) => {
  tx.text.insert("Hello");
});

On This Page

Node options

Node methods accept method-specific options objects. These options appear across most node methods:

type NodeSelectorOptions = {
  at?: NodeTarget;
  match?: NodeMatch<Node>;
  mode?: "highest" | "lowest" | "all";
  voids?: boolean;
};
type NodeSelectorOptions = {
  at?: NodeTarget;
  match?: NodeMatch<Node>;
  mode?: "highest" | "lowest" | "all";
  voids?: boolean;
};
  • at?: NodeTarget: An explicit location or live descendant to change. When omitted inside editor.update(...), selection-sensitive methods use the transaction target.
  • match?: NodeMatch<Node>: A predicate or shallow property object that filters which nodes are changed. Array property values mean one-of.
  • mode?: 'highest' | 'lowest': Controls which matching node level is used. Methods documented with mode?: 'highest' | 'lowest' | 'all' also accept all.
  • voids?: boolean: Includes void elements when true.

Node methods

Use node methods from tx.nodes.

tx.nodes.insert(nodes: Node | Node[], options?)

Insert nodes at options.at or the transaction target.

Options: at, match, mode, hanging, select, voids, batchDirty.

  • hanging?: boolean: Preserve hanging range edges.
  • select?: boolean: Select the inserted nodes.
  • batchDirty?: boolean: Batch dirty-path work for a multi-node insert.
editor.update((tx) => {
  tx.nodes.insert({ type: targetType, children: [{ text: "" }] }, { at: [0] });
});
editor.update((tx) => {
  tx.nodes.insert({ type: targetType, children: [{ text: "" }] }, { at: [0] });
});

tx.nodes.remove(options?)

Remove nodes at options.at or the transaction target.

Options: at, match, mode, hanging, voids.

tx.nodes.merge(options?)

Merge a node with the previous node at the same depth.

Options: at, match, mode, hanging, voids.

tx.nodes.split(options?)

Split nodes at a location.

Options: at, match, mode, always, height, position, voids.

  • always?: boolean: Split even when the target is already at an edge.
  • height?: number: Split at an ancestor height.
  • position?: number: Split at an explicit child position.

tx.nodes.wrap(element: Element, options?)

Wrap matching nodes in element.

Options: at, match, mode, split, voids.

tx.nodes.unwrap(options?)

Unwrap matching nodes.

Options: at, match, mode, split, voids.

tx.nodes.set(props: Partial<Node>, options?)

Set properties on matching nodes.

Options: at, match, mode, hanging, split, voids, compare, merge.

  • compare?: PropsCompare: Decide whether a property should be written.
  • merge?: PropsMerge: Merge incoming and existing property values.

Pass an exact node as at to preserve its property inference.

editor.update.nodes.set({ icon: "🔥" }, { at: calloutElement });
editor.update.nodes.set({ icon: "🔥" }, { at: calloutElement });

tx.nodes.unset(props: string | string[], options?)

Unset properties on matching nodes.

Options: at, match, mode, hanging, split, voids.

tx.nodes.lift(options?)

Lift matching nodes upward in the document tree.

Options: at, match, mode, voids.

tx.nodes.move(options)

Move nodes from options.at to options.to.

Options: at, match, mode, to, voids.

  • to: Path: Destination path for the moved nodes.

Block methods

Use semantic block mutations from tx.blocks inside a transaction or from editor.update.blocks for a direct update.

tx.blocks.insertAfter(blocks, options?)

Insert one or more block elements after the block containing options.at. When at is omitted, the transaction selection is the reference. Ranges use their document-order end. Missing or blockless references are no-ops.

Options:

  • at?: NodeTarget: Reference location or live descendant.
  • select?: boolean: Select the inserted blocks.
editor.update.blocks.insertAfter(
  { type: "callout", children: [{ text: "" }] },
  { at: calloutElement, select: true }
);
editor.update.blocks.insertAfter(
  { type: "callout", children: [{ text: "" }] },
  { at: calloutElement, select: true }
);

tx.blocks.duplicate(options?)

Duplicate the targeted blocks after the last matching block.

tx.blocks.lift(options?)

Lift the targeted blocks one structural level.

tx.blocks.reset(props, options?)

Reset a block to props, removing other block properties unless their keys are listed in options.preserve.

tx.blocks.toggle(type, options?)

Toggle targeted blocks between type and the editor default block type.

Break methods

Use break methods from tx.break.

tx.break.insert()

Insert a block break at the transaction target.

tx.break.insertSoft()

Insert a soft break at the transaction target.

Fragment methods

Use fragment methods from tx.fragment.

tx.fragment(options?)

Read the fragment at options.at or the current selection.

Options:

  • at?: Range: Range to read. Defaults to the active selection.

tx.fragment.insert(fragment: Node[], options?)

Insert a fragment at options.at or the transaction target. Plite's default insertion policy is structural. When a fragment contains nested blocks that match the active structural container, the first compatible source block can merge into the active block and later source siblings stay as inserted siblings. Product-specific positional behavior, such as multi-cell table-grid paste, belongs in an extension clipboard/fragment policy.

Options:

  • at?: NodeTarget: Where to insert. Defaults to the transaction target.
  • hanging?: boolean: Preserve hanging range edges instead of un-hanging first.
  • voids?: boolean: Allow insertion into void elements.
  • batchDirty?: boolean: Batch dirty-path work for the fragment insert.

tx.fragment.delete(options?)

Delete the fragment at options.at or the transaction target.

Options:

  • at?: NodeTarget: Location or live descendant to delete. Defaults to the transaction target.
  • direction?: 'forward' | 'backward': Direction used for collapsed deletion.

Text methods

tx.text.insert(text: string, options?)

Insert text at options.at or the transaction target.

Options:

  • at?: NodeTarget: Where to insert. Defaults to the transaction target.
  • voids?: boolean: Allow insertion into void elements.

When at is an expanded range, the inserted text replaces the range and the selection moves after the inserted text.

tx.text.delete(options?)

Delete text at options.at or the transaction target.

Options:

  • at?: NodeTarget: Location or live descendant to delete from. Defaults to the transaction target.
  • distance?: number: Number of units to delete. Defaults to one unit.
  • unit?: 'character' | 'word' | 'line' | 'block': Unit used for collapsed deletion.
  • reverse?: boolean: Delete before the target instead of after it.
  • hanging?: boolean: Preserve hanging range edges instead of un-hanging first.
  • voids?: boolean: Allow deletion inside void elements.
editor.update((tx) => {
  tx.text.delete({ reverse: true, unit: "word" });
});
editor.update((tx) => {
  tx.text.delete({ reverse: true, unit: "word" });
});

tx.text.deleteBackward(options?)

Delete before the transaction target.

Options:

  • unit?: 'character' | 'word' | 'line' | 'block': Unit to delete. Defaults to one character.

tx.text.deleteForward(options?)

Delete after the transaction target.

Options:

  • unit?: 'character' | 'word' | 'line' | 'block': Unit to delete. Defaults to one character.

Selection methods

tx.selection reads the transaction selection and exposes the same query predicates as editor.read.selection: contains(target), intersects(target), isCollapsed(), isExpanded(), isWithinText(), isWithinBlock(options?), isAcrossBlocks(options?), isAtBlockStart(options?), and isAtBlockEnd(options?).

tx.selection.set(target: Location | null)

Set the selection to a new target.

editor.update((tx) => {
  tx.selection.set({
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [1, 0], offset: 0 },
  });
});
editor.update((tx) => {
  tx.selection.set({
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [1, 0], offset: 0 },
  });
});

tx.selection.clear()

Clear the selection.

tx.selection.collapse(options?)

Collapse the selection to a single point.

Options:

  • edge?: 'anchor' | 'focus' | 'start' | 'end': Edge to collapse to.

tx.selection.move(options?)

Move the selection by offset, character, word, line, or block.

Options:

  • distance?: number: Number of units to move.
  • unit?: 'offset' | 'character' | 'word' | 'line': Unit used for movement.
  • reverse?: boolean: Move backward.
  • edge?: 'anchor' | 'focus' | 'start' | 'end': Selection edge to move.

tx.selection.setPoint(props: Partial<Point>, options?)

Set properties on one selection point.

Options:

  • edge?: 'anchor' | 'focus' | 'start' | 'end': Selection edge to update.

tx.selection.setRange(props: Partial<Range>)

Set properties on an active selection.

Mark methods

tx.marks()

Return the active marks for the transaction.

tx.marks.add(key: string, value: unknown)

Add a mark to the current selection or pending marks.

tx.marks.remove(key: string)

Remove a mark from the current selection or pending marks.

tx.marks.set(marks: Record<string, unknown> | null)

Replace the pending insertion marks. Use {} to force plain inserted text at a collapsed selection, or null to let inserted text inherit from the selected text position.

tx.marks.toggle(key: string, value?, options?)

Toggle a mark for the current selection or pending marks.

  • options.clear?: string | string[]: Remove mutually exclusive marks before enabling the target mark. If the target mark is active, only the target mark is removed.

Value and root methods

Use value and root methods for whole-root or whole-document replacement. Normal typing commands should use tx.text, tx.nodes, tx.fragment, or tx.selection.

For one-shot snapshot imports, call the direct update method:

editor.update({ history: "skip" }).value.replace({
  children: [{ type: "paragraph", children: [{ text: "Imported" }] }],
  selection: "end",
});
editor.update({ history: "skip" }).value.replace({
  children: [{ type: "paragraph", children: [{ text: "Imported" }] }],
  selection: "end",
});

selection accepts a range, null, "start", or "end". Configure history and tags through editor.update(policy) before calling the direct method.

tx.value.replace(input: SnapshotInput)

Replace the active root or complete snapshot inside a larger transaction.

editor.update((tx) => {
  tx.value.replace({
    children: [{ type: "paragraph", children: [{ text: "Imported" }] }],
    selection: null,
  });
});
editor.update((tx) => {
  tx.value.replace({
    children: [{ type: "paragraph", children: [{ text: "Imported" }] }],
    selection: null,
  });
});

Pass roots in the input when replacing multiple roots at once, or combine tx.value.replace, tx.roots, and tx.setField when the import also updates document meta fields.

tx.roots.create(root: RootKey, children: Node[])

Create a named root.

tx.roots.replace(root: RootKey, children: Node[])

Replace an existing named root.

tx.roots.delete(root: RootKey)

Delete an extra root. The primary document is not deleted through tx.roots.

State field methods

tx.setField(field, value)

Write a registered state field.

editor.update((tx) => {
  tx.setField(documentTitle, "Q3 Launch Brief");
});
editor.update((tx) => {
  tx.setField(documentTitle, "Q3 Launch Brief");
});

State-field writes appear in commit.statePatches.

tx.statePatches.replay(statePatches)

Replay accepted state-field patches through the transaction boundary.

Filter remote or collaborative patches before replaying them.

Normalization methods

tx.normalize(options?)

Run an explicit normalization barrier inside the active transaction. Normal grouped updates already normalize before commit; use this only when subsequent transaction logic must read the repaired tree.

Operation methods

tx.operations.replay(operations: Operation[])

Replay operations through the transaction boundary.

editor.update({ tags: "remote-import" }, (tx) => {
  tx.operations.replay(remoteOperations);
});
editor.update({ tags: "remote-import" }, (tx) => {
  tx.operations.replay(remoteOperations);
});