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.Use node methods from tx.nodes.
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] });
});Remove nodes at options.at or the transaction target.
Options: at, type, match, mode, hanging, voids.
Merge a node with the previous node at the same depth.
Options: at, type, match, mode, hanging, voids.
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.Wrap matching nodes in element. Returns whether the document changed. With no match or type, an inline element over a range wraps only where the element that would hold the wrapper accepts it and the wrapper accepts the nodes it would move. It returns false when it can wrap nowhere in the range. With split, it runs from the first character or inline void the range holds to the last. It leaves out an inline element the range only touches at its edge and every block whose part of the range holds none. It also stops short of a read-only inline at either end, though an inline element around one can still move into the wrapper whole. That wrap ignores voids, so it never wraps anything inside a void or read-only element. In that wrap, an inline element the range crosses into or out of moves into the wrapper whole, and a range inside one inline element wraps inside it. With split, a wrap with match or type splits at the range's edges. When one match holds another, as mode: 'all' allows, the wrapper goes around the outer one, even when it holds text outside the range.
Options: at, type, match, mode, split, voids.
Unwrap matching nodes. Returns whether the document changed. With split, it cuts an inline element that holds text, such as a link, at the range's edges and unwraps only the selected part; at a caret or a point it unwraps the whole element.
Options: at, type, match, mode, split, voids.
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.
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.
Lift matching nodes upward in the document tree.
Options: at, type, match, mode, voids.
Move nodes from options.at to options.to.
Options: at, type, match, mode, to, voids.
to: Path: Destination path for the moved nodes.Use semantic block mutations from tx.blocks inside a transaction or from
editor.update.blocks for a direct update.
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; node selections use their final selected block.
An absent selection appends to the current root. An unresolved explicit target
is a no-op. Returns the first inserted path, or undefined if nothing was inserted.
Options:
at?: NodeSelectionTarget: Reference location, live descendant, node key, or node selection.select?: boolean: Select the inserted blocks.replaceEmpty?: boolean: Replace an empty editable, non-atomic text block
in the same transaction. Nonempty sources and empty structural or atomic
blocks remain intact.editor.update.blocks.insertAfter(
{ type: "callout", children: [{ text: "" }] },
{ at: calloutElement, select: true }
);editor.update.blocks.insertAfter(
{ type: "callout", children: [{ text: "" }] },
{ at: calloutElement, select: true }
);Duplicate the targeted blocks after the last matching block.
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.
Reset checks every target before it writes. When a default is not allowed at
one target's position, such as a heading the parent requires first, it returns
false and changes nothing; otherwise it returns true. The update does not
roll back on false, so check the result before composing other edits.
Options:
at: A document location, live node or key, or exact node selection.
Defaults to the active text or exact node selection.unset: Property names or a schema property handle to remove when the
default does not set them, such as a level a block keeps when it is already
its parent's default.editor.update((tx) => {
if (!tx.blocks.reset({ unset: ["level"] })) return;
tx.plugin(BaseListPlugin).clear();
});editor.update((tx) => {
if (!tx.blocks.reset({ unset: ["level"] })) return;
tx.plugin(BaseListPlugin).clear();
});The parent or root grammar must declare an element default.
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.
props can also be a function that receives each targeted block and returns
its own patch. Every patch is resolved and checked before any is written, so
the selection changes as a whole or not at all. Returns false when a patch is
not allowed or nothing changes.
editor.update.blocks.set((node) =>
node.type === "summary" ? { level: 2 } : { level: 2, type: "heading" }
);editor.update.blocks.set((node) =>
node.type === "summary" ? { level: 2 } : { level: 2, type: "heading" }
);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.
Use break methods from tx.break.
Leave the nearest structure around the selected block and insert the default block that the structure's parent declares at that position.
The selected block is the structure when it owns its exit: a void, an object,
or a text block whose schema declares ownsExit, such as a code block. It
stays the structure only when its parent can build a default beside it that
does not put the caret inside another block that owns its exit.
Otherwise the walk starts at the selected block's parent. It stops at the first ancestor that sits directly in a root, sits in a parent that declares no content, or sits in a parent whose content after any required leading blocks admits a text block, such as a paragraph. A table row holds only cells, so the walk passes a cell and its row and stops at the table. The walk also passes an ancestor that does not sit directly in a root when its default would put the caret back inside an element of the ancestor's own type. With no such ancestor, the selected block is the structure.
The method inserts the new block after the structure, or before it with
before: true, and selects it.
editor.update.break.exit();editor.update.break.exit();Returns false without changing the document when the selection is not a
collapsed caret, or when the structure's parent declares no default block, has
no room for it, cannot fill a required property of that default, or would give
the caret no text to land in. Any other failure to build the default throws.
Options:
before?: boolean: Insert before the structure instead of after it.match?: (node, path) => boolean: Limit which levels can be the structure:
the selected block when it owns its exit, then its ancestors. The selected
block is the fallback.Insert a block break at the transaction target.
Insert a soft break at the transaction target.
Use fragment methods from tx.fragment.
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.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.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.Within a callback transaction, use tx.slice to fit parsed or imported content
at its replacement range. Use editor.update.slice.replace when installed
paste policy must also run.
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.The one-shot editor.update.slice.replace and editor.update.fragment.replace
methods run command handlers and the
paste landing read.
Their callback transaction forms fit content directly and bypass that command
policy, as does editor.read.slice.fit.
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.
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" });
});Delete before the transaction target.
Options:
unit?: 'character' | 'word' | 'line' | 'block': Unit to delete. Defaults
to one character.Delete after the transaction target.
Options:
unit?: 'character' | 'word' | 'line' | 'block': Unit to delete. Defaults
to one character.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?).
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 },
});
});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);
});Clear the selection.
Collapse the selection to a single point.
Options:
edge?: 'anchor' | 'focus' | 'start' | 'end': Edge to collapse to.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.Set properties on one selection point.
Options:
edge?: 'anchor' | 'focus' | 'start' | 'end': Selection edge to update.Return the active marks for the transaction.
Add a mark to the current selection or pending marks.
Remove a mark from the current selection or pending marks.
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.
Toggle a mark for the current selection or pending marks. Mutual exclusion belongs to the text-property declarations in the editor schema.
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.
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. It holds children,
roots, meta and selection, or a persisted envelope of document, schema
and selection, and replace refuses any other field. 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.
Create a named root.
Replace an existing named root.
Delete an extra root. The primary document is not deleted through tx.roots.
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.
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.
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.
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));
});