ianstormtaylor/slate · error · Error
Index must be number
Error message
Index must be number
What it means
A defensive check inside merge_node: the final index of the path (and of the previous sibling's path) must be a number. It throws when the operation's path contains a string segment, as can happen with deserialized or maliciously crafted operations.
Source
Thrown at packages/slate/src/interfaces/transforms/general.ts:105
transformSelection = true
break
}
case 'merge_node': {
const { path } = op
const index = path[path.length - 1]
const prevPath = Path.previous(path)
const prevIndex = prevPath[prevPath.length - 1]
if (path.length === 0) {
throw new Error(
`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)}`
)
}
View on GitHub (pinned to 72a37c701e)
Solutions
- Validate/normalize operation paths before applying: ensure every segment is a number.
- Sanitize deserialized operations with a schema check (e.g. zod) that coerces or rejects string segments.
- Fix the code building the path — avoid template literals that yield strings.
Example fix
// before
const op = JSON.parse(saved) // path segments may be strings
Editor.apply(editor, op)
// after
const op = JSON.parse(saved)
if (!op.path?.every((n: unknown) => typeof n === 'number')) {
throw new Error('Invalid operation path')
}
Editor.apply(editor, op) Defensive patterns
Strategy: validation
Validate before calling
const valid = op.path.every((n: unknown) => typeof n === 'number') if (valid) Editor.apply(editor, op)
Type guard
function isNumericPath(path: unknown[]): path is Path {
return path.every(n => typeof n === 'number' && Number.isInteger(n) && n >= 0)
} Try / catch
try {
Editor.apply(editor, op)
} catch (e) {
if (e instanceof Error && e.message === 'Index must be number') {
// reject/sanitize the incoming operation
} else throw e
} Prevention
- Schema-validate deserialized operations (e.g. zod) before applying.
- Never build paths with template literals; keep segments as numbers.
- Treat peer-supplied operations as untrusted input.
When it happens
Trigger: Applying a merge_node whose path contains non-numeric segments, e.g. path: ['a', 1]; typically from JSON.parse'd operations from untrusted sources, deserialized drafts, or a bug building paths from template strings.
Common situations: Loading operation logs from localStorage/backend where a path got stringified; accepting operations from a collaborative peer without validation; constructing paths with template literals ('0' instead of 0).
Related errors
- Got non-numeric path index
- Got non-numeric path index
- Cannot get the relative path of [${path}] inside ancestor [$
- Cannot apply a "merge_node" operation at path [${path}] beca
- Cannot apply a "merge_node" operation at path [${path}] to n
AI-assisted analysis of ianstormtaylor/slate@72a37c701e (2026-08-27).
Data as JSON: /api/errors/4996439b4751ecde.
Report an issue: GitHub.