{"record":{"id":"97b2544b6b9b9f6c","repo":"BookStackApp/BookStack","slug":"row-before-insertion-index-does-not-exist","errorCode":null,"errorMessage":"Row before insertion index does not exist.","messagePattern":"Row before insertion index does not exist\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"resources/js/wysiwyg/lexical/table/LexicalTableUtils.ts","lineNumber":230,"sourceCode":"        ) {\n          headerState |= TableCellHeaderStates.COLUMN;\n        }\n\n        const tableCellNode = $createTableCellNode(headerState, 1, width);\n\n        tableCellNode.append($createParagraphNode());\n\n        newTableRowNode.append(tableCellNode);\n      }\n\n      if (shouldInsertAfter) {\n        targetRowNode.insertAfter(newTableRowNode);\n      } else {\n        targetRowNode.insertBefore(newTableRowNode);\n      }\n    }\n  } else {\n    throw new Error('Row before insertion index does not exist.');\n  }\n\n  return tableNode;\n}\n\nconst getHeaderState = (\n  currentState: TableCellHeaderState,\n  possibleState: TableCellHeaderState,\n): TableCellHeaderState => {\n  if (\n    currentState === TableCellHeaderStates.BOTH ||\n    currentState === possibleState\n  ) {\n    return possibleState;\n  }\n  return TableCellHeaderStates.NO_STATUS;\n};\n","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/resources/js/wysiwyg/lexical/table/LexicalTableUtils.ts#L212-L248","documentation":"$insertTableRow inserts rowCount new rows relative to targetIndex in the TableNode. After the basic range check, it requires the child at targetIndex to actually be a TableRowNode; if it is a TableRowNode it proceeds, otherwise this error is thrown. It fires when the table's children are not all row nodes, i.e. the target index points at unexpected or malformed child content.","triggerScenarios":"Calling $insertTableRow(tableNode, targetIndex, ...) where tableRows[targetIndex] fails $isTableRowNode — e.g. targetIndex is valid numerically but the child at that position is not a TableRowNode (custom/non-row child appended to the table, or table structure corrupted by other transforms).","commonSituations":"Custom editor plugins appending non-row children directly to a TableNode; DOM/parser output producing tables whose children don't match Lexical's expected schema; using the low-level $insertTableRow instead of the selection-based $insertTableRow__EXPERIMENTAL with a stale or mutated table.","solutions":["Validate table children before inserting: every child of the TableNode should pass $isTableRowNode; remove or normalize any non-row child.","Prefer $insertTableRow__EXPERIMENTAL() which derives the target row from the current selection instead of a hand-computed index.","Recompute targetIndex from the live TableNode (tableNode.getChildren()) immediately before the call rather than caching it.","Wrap the call in try/catch inside your update and surface a user-facing message instead of crashing the editor transaction."],"exampleFix":"// before\nconst rows = tableNode.getChildren();\n$insertTableRow(tableNode, rows.length - 1, true, 1, table);\n// after\nconst rows = tableNode.getChildren();\nconst targetIndex = rows.findIndex($isTableRowNode);\nif (targetIndex === -1) return;\n$insertTableRow(tableNode, targetIndex, true, 1, table);","handlingStrategy":"validation","validationCode":"function canInsertRow(tableNode) {\n  const rows = tableNode.getChildren();\n  return rows.length > 0 && rows.every($isTableRowNode);\n}","typeGuard":"function isInsertableTable(node) {\n  return $isTableNode(node) && node.getChildren().every((c) => $isTableRowNode(c));\n}","tryCatchPattern":"try {\n  editor.update(() => $insertTableRow(tableNode, targetIndex, true, 1, table));\n} catch (e) {\n  if (e.message === 'Row before insertion index does not exist.') {\n    // log and skip; table structure is not row-pure\n  } else throw e;\n}","preventionTips":["Always call table helpers inside editor.update() and re-read children immediately before use.","Prefer the __EXPERIMENTAL selection-based variants over manual index arithmetic.","Assert every TableNode child is a TableRowNode after custom transforms and parser passes."],"tags":["lexical","table","index-out-of-range","editor"],"backgroundTag":"table-index-out-of-range","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}