From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
UNPUBLISHED
  • Overview
  • Why This Fork
  • Examples
Walkthroughs
  • Installing Plite
  • Adding Event Handlers
  • Defining Custom Elements
  • Applying Custom Formatting
  • Executing Commands
  • Saving to a Database
  • Canonical Change Substrate
  • Improving Performance
Concepts
  • Interfaces
  • Nodes
  • Locations
  • Transforms
  • Document Changes
  • Commands
  • Editor
  • Extensions
  • Rendering
  • Serializing
  • Normalizing
  • Using TypeScript
  • Roots
  • Document State
  • Editing Behavior
  • Selection And DOM
  • Clipboard And Paste
  • Projection And Overlays
  • Schema
API
  • 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
Libraries
  • Plite DOM
  • History Editor API
  • History Extension Setup
  • History
  • Plite History
  • Plite Hyperscript
  • Plite Layout
  • Annotations
  • DOM Coverage Boundaries
  • Editable Component
  • Plite React Event Handling
  • Virtualized Rendering
  • Plite React Hooks
  • React Editor Setup
  • React Editor
  • Plite React
  • Plite Component
  • Plite Yjs
  • Plite
General
  • Migration
  • Contributing
  • Docs Proof Map
  • FAQ
  • Resources

Transforms API

PreviousNext

Transaction transform groups for node, text, selection, mark, root, state, effect, and canonical change writes.

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

Basic Usage

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

On This Page

Basic UsageOn This PageNode optionsNode methodstx.nodes.insert(nodes: Node | Node[], options?)tx.nodes.remove(options?)tx.nodes.merge(options?)tx.nodes.split(options?)tx.nodes.wrap(element: Element, options?)tx.nodes.unwrap(options?)tx.nodes.set(props, options?)tx.nodes.unset(props, options?)tx.nodes.lift(options?)tx.nodes.move(options)Block methodstx.blocks.insertAfter(blocks, options?)tx.blocks.duplicate(options?)tx.blocks.reset(options?)tx.blocks.set(props, options?)tx.blocks.toggle(props, options?)Break methodstx.break.insert()tx.break.insertSoft()Fragment methodstx.fragment(options?)tx.fragment.delete(options?)tx.fragment.replace(content, options?)Slice methodstx.slice.replace(slice: ContentSlice, options?)Text methodstx.text.insert(text: string, options?)tx.text.delete(options?)tx.text.deleteBackward(options?)tx.text.deleteForward(options?)Selection methodstx.selection.set(target: Location | null)tx.selection.setNodes(targets: readonly (Path | NodeKey | Descendant)[])tx.selection.set(null)tx.selection.collapse(options?)tx.selection.move(options?)tx.selection.setPoint(props: Partial<Point>, options?)Mark methodstx.marks()tx.marks.add(key: string, value: unknown)tx.marks.remove(key: string)tx.marks.set(marks: Record<string, unknown> | null)tx.marks.toggle(key: string, value?)Value and root methodstx.value.replace(input: SnapshotInput)tx.roots.create(root: RootKey, children: Node[])tx.roots.replace(root: RootKey, children: Node[])tx.roots.delete(root: RootKey)State field methodstx.setField(field, value)Effect methodstx.effects.emit(type, value)Correction methodseditor.update.value.repair()Canonical change methodstx.changes.apply(change: DocumentChange)
Build your editor
Production-ready AI template and reusable components.
Get all-access
(
"Hello"
);
});

On This Page

  • Node options
  • Node methods
  • Block methods
  • Break methods
  • Fragment methods
  • Slice methods
  • Text methods
  • Selection methods
  • Mark methods
  • Value and root methods
  • State field methods
  • Correction methods
  • Effect methods
  • Canonical change methods

Node options

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

type NodeSelectorOptions = {
  at?: NodeTarget;
  type?: NodeTypeSelector;
  match?: NodeMatch<Node>;
  mode?: "highest" | "lowest" | "all";
  voids?: boolean;
};
type NodeSelectorOptions = {
  at?: NodeTarget;
  type?: NodeTypeSelector;
  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.
  • type?: NodeTypeSelector: Select one structural type or any type in an array.
  • match?: NodeMatch<Node>: A predicate that adds computed conditions. The selected type determines the predicate's node type.
  • 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, mode, hanging, select, split, voids.

  • hanging?: boolean: Preserve hanging range edges.
  • select?: boolean: Select the inserted nodes.
  • split?: { type?, match? }: Select which ancestor to split before insertion.
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, type, match, mode, hanging, voids.

tx.nodes.merge(options?)

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

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

tx.nodes.split(options?)

Split nodes at a location.

Options: at, type, 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, type, match, mode, split, voids.

tx.nodes.unwrap(options?)

Unwrap matching nodes.

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

tx.nodes.set(props, options?)

Set properties on matching nodes.

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

Options: at, type, 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 });

Set one or several properties in the same patch. Their values are inferred from the editor's closed Value type:

editor.update.nodes.set({ indent: 2 });
editor.update.nodes.set({ indent: 2 });

Use undefined to remove a property in the same atomic patch:

editor.update.nodes.set({ indent: undefined, listStyle: undefined });
editor.update.nodes.set({ indent: undefined, listStyle: undefined });

For an aliased persisted property, use its exact schema key as a computed object key. Prefix handles cannot address one property.

tx.nodes.unset(props, options?)

Unset properties on matching nodes.

tx.nodes.unset(key | key[], options?);
tx.nodes.unset(propertyHandle, options?);
tx.nodes.unset(key | key[], options?);
tx.nodes.unset(propertyHandle, options?);

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

tx.nodes.lift(options?)

Lift matching nodes upward in the document tree.

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

tx.nodes.move(options)

Move nodes from options.at to options.to.

Options: at, type, 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.reset(options?)

Reset each targeted block to the default element declared by its immediate parent, or by its document root for a top-level block. The mutation preserves the block's children, selection, and live node key. Element properties survive only when their schema lifecycle uses typeChange: "preserve-if-allowed" and the destination element accepts them.

Options:

  • at: A document location, live node or key, or exact node selection. Defaults to the active text or exact node selection.
editor.update.blocks.reset();
editor.update.blocks.reset();

The parent or root grammar must declare an element default.

tx.blocks.set(props, options?)

Set properties on the targeted schema blocks. When at is omitted, the active selection is the target. Use tx.nodes.set when the target is not specifically a block.

tx.blocks.toggle(props, options?)

Toggle targeted blocks between the complete props shape and their immediate parent or document-root default block shape.

editor.update.blocks.toggle({ type: "heading", level: 2 });
editor.update.blocks.toggle({ type: "heading", level: 2 });

Pass { wrap: true } to wrap or unwrap the targeted blocks instead.

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.
  • unwrap?: readonly string[]: Omit matching wrapper element types from the returned fragment.

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.

tx.fragment.replace(content, options?)

Fit and replace known-closed content at options.at or the transaction target. The method returns false without mutating when the target cannot be resolved, is a protected void, or the content cannot fit the compiled schema.

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

Options:

  • at?: NodeTarget: Replacement range, point, path, or live descendant.
  • hanging?: boolean: Preserve hanging range edges instead of un-hanging first.
  • voids?: boolean: Allow replacement inside void elements.

Slice methods

Use tx.slice for parsed or imported content whose open edges must be fitted against the compiled schema at its actual replacement range.

tx.slice.replace(slice: ContentSlice, options?)

Fit and atomically replace content at options.at or the transaction target. The slice's optional roots map carries detached transitive secondary roots; insertion remaps copied keys deterministically and preserves shared aliases. The method returns false without mutating when the target cannot be resolved, is a protected void, or a well-formed slice cannot fit. Malformed slice shapes are programmer errors and throw. The method returns true once a fitted replacement is accepted, including a valid replacement whose canonical change is empty.

Options:

  • at?: NodeTarget: Replacement range, point, path, or live descendant.
  • hanging?: boolean: Preserve hanging range edges instead of un-hanging first.
  • voids?: boolean: Allow replacement inside void elements.

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(), 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.setNodes(targets: readonly (Path | NodeKey | Descendant)[])

Select live nodes in the transaction draft. The transaction resolves every target in one root and canonicalizes exact membership. An empty collection clears the selection.

editor.update((tx) => {
  tx.nodes.insert(blocks, { at: [2] });
  tx.selection.setNodes(blocks);
});
editor.update((tx) => {
  tx.nodes.insert(blocks, { at: [2] });
  tx.selection.setNodes(blocks);
});

tx.selection.set(null)

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.

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?)

Toggle a mark for the current selection or pending marks. Mutual exclusion belongs to the text-property declarations in the editor schema.

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 complete serializable document 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,
  });
});

The input is complete: omitted named roots are deleted and omitted persisted meta fields reset to their declared initial values. Omitted selection is cleared. Invalid combined content returns false and publishes nothing. Use tx.roots for a partial named-root change and tx.setField for one metadata field.

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 emit the field's typed effect and list the field key in commit.dirtyStateKeys.

Effect methods

tx.effects.emit(type, value)

Emit a typed effect. Installed state fields reduce effects during the same transaction. Effect descriptors define history inversion, collaboration policy, and mapping through canonical document changes.

Correction methods

editor.update.value.repair()

Repair every document root with the installed corrections. This maintenance method starts a history-skipped update, cannot run inside another update, and publishes no commit when the value is already canonical. Ordinary grouped updates run deterministic corrections over ranges changed by the transaction.

Canonical change methods

tx.changes.apply(change: DocumentChange)

Apply one root-aware canonical document change to the active draft. This is the adapter and history boundary for serialized or remote changes; ordinary editing uses semantic transaction groups.

editor.update({ tags: "remote-import" }, (tx) => {
  tx.changes.apply(DocumentChange.fromJSON(remoteChange));
});
editor.update({ tags: "remote-import" }, (tx) => {
  tx.changes.apply(DocumentChange.fromJSON(remoteChange));
});