ianstormtaylor/slate · error · Error

Cannot apply a "merge_node" operation at path [${path}] to n

Error message

Cannot apply a "merge_node" operation at path [${path}] to nodes of different interfaces: ${Scrubber.stringify(
                node
              )} ${Scrubber.stringify(prev)}

What it means

merge_node merges a node into its previous sibling, which only works when both are text nodes or both are element nodes. It throws when one is a text node and the other an element (or otherwise incompatible interfaces), since there is no defined way to merge them.

Source

Thrown at packages/slate/src/interfaces/transforms/general.ts:117

            `Cannot apply a "merge_node" operation at path [${path}] because the root node cannot be merged.`
          )
        }

        // Defend against malicious paths containing strings
        if (typeof index !== 'number' || typeof prevIndex !== 'number')
          throw new Error('Index must be number')

        modifyChildren(editor, Path.parent(path), children => {
          const node = children[index]
          const prev = children[prevIndex]
          let newNode: Descendant

          if (Node.isText(node) && Node.isText(prev)) {
            newNode = { ...prev, text: prev.text + node.text }
          } else if (Node.isElement(node) && Node.isElement(prev)) {
            newNode = { ...prev, children: prev.children.concat(node.children) }
          } else {
            throw new Error(
              `Cannot apply a "merge_node" operation at path [${path}] to nodes of different interfaces: ${Scrubber.stringify(
                node
              )} ${Scrubber.stringify(prev)}`
            )
          }

          return replaceChildren(children, prevIndex, 2, newNode)
        })

        transformSelection = true
        break
      }

      case 'move_node': {
        const { path, newPath } = op
        const index = path[path.length - 1]

        if (Path.isAncestor(path, newPath)) {

View on GitHub (pinned to 72a37c701e)

Solutions

  1. Before merging, check that Node.isText matches for both the node and its previous sibling; if they differ, use Transforms.wrapNodes/unwrapNodes or remove one instead.
  2. Run your editor with a schema/normalizer so parents only ever contain homogeneous mergeable children.
  3. Validate the document structure after deserializing HTML/Markdown before applying merges.

Example fix

// before
Transforms.mergeNodes(editor, { at: path })

// after
const node = Node.get(editor, path)
const prev = Node.get(editor, Path.previous(path))
if (Node.isText(node) === Node.isText(prev)) {
  Transforms.mergeNodes(editor, { at: path })
} else {
  Transforms.removeNodes(editor, { at: path })
}
Defensive patterns

Strategy: type-guard

Validate before calling

const node = Node.get(editor, path)
const prev = Node.get(editor, Path.previous(path))
if (Node.isText(node) === Node.isText(prev)) {
  Transforms.mergeNodes(editor, { at: path })
}

Type guard

function areMergeable(a: Node, b: Node): boolean {
  return (Node.isText(a) && Node.isText(b)) || (Node.isElement(a) && Node.isElement(b))
}

Try / catch

try {
  Transforms.mergeNodes(editor, { at: path })
} catch (e) {
  if (e instanceof Error && e.message.includes('different interfaces')) {
    Transforms.removeNodes(editor, { at: path })
  } else throw e
}

Prevention

When it happens

Trigger: Applying merge_node at a path whose node is a text node while the previous sibling is an element, or vice versa — e.g. merging path [1] where [0] is an element and [1] is a text node; often from hand-built operations or a document whose children violate Slate's content schema.

Common situations: Documents with mixed invalid children (element containing both element and text children directly where schema forbids it); custom normalizers that merge nodes without checking node types; replaying operations against a diverged document.

Related errors


AI-assisted analysis of ianstormtaylor/slate@72a37c701e (2026-08-27). Data as JSON: /api/errors/218273dfcd5a6202. Report an issue: GitHub.