{"record":{"id":"da0882046d07912f","repo":"BookStackApp/BookStack","slug":"table-column-target-index-out-of-range","errorCode":null,"errorMessage":"Table column target index out of range","messagePattern":"Table column target index out of range","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"resources/js/wysiwyg/lexical/table/LexicalTableUtils.ts","lineNumber":335,"sourceCode":"\nexport function $insertTableColumn(\n  tableNode: TableNode,\n  targetIndex: number,\n  shouldInsertAfter = true,\n  columnCount: number,\n  table: TableDOMTable,\n): TableNode {\n  const tableRows = tableNode.getChildren();\n\n  const tableCellsToBeInserted = [];\n  for (let r = 0; r < tableRows.length; r++) {\n    const currentTableRowNode = tableRows[r];\n\n    if ($isTableRowNode(currentTableRowNode)) {\n      for (let c = 0; c < columnCount; c++) {\n        const tableRowChildren = currentTableRowNode.getChildren();\n        if (targetIndex >= tableRowChildren.length || targetIndex < 0) {\n          throw new Error('Table column target index out of range');\n        }\n\n        const targetCell = tableRowChildren[targetIndex];\n\n        invariant($isTableCellNode(targetCell), 'Expected table cell');\n\n        const {left, right} = $getTableCellSiblingsFromTableCellNode(\n          targetCell,\n          table,\n        );\n\n        let headerState = TableCellHeaderStates.NO_STATUS;\n\n        if (\n          (left && left.hasHeaderState(TableCellHeaderStates.ROW)) ||\n          (right && right.hasHeaderState(TableCellHeaderStates.ROW))\n        ) {\n          headerState |= TableCellHeaderStates.ROW;","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/resources/js/wysiwyg/lexical/table/LexicalTableUtils.ts#L317-L353","documentation":"$insertTableColumn iterates every TableRowNode and inserts columnCount new cells relative to targetIndex within each row. Because each row must supply a cell at targetIndex, the index must be a valid position in every row's children; if targetIndex >= the row's cell count or is negative, this error is thrown. It guards against inserting a column at a position that does not line up across all rows.","triggerScenarios":"Calling $insertTableColumn(tableNode, targetIndex, ...) where at least one row has fewer cells than targetIndex+1 (e.g. rows built from colspan-bearing DOM where each TableRowNode holds fewer TableCellNodes than the visual column count), or passing targetIndex < 0.","commonSituations":"Computing targetIndex from the visual HTML column count while rows contain merged/colspan cells so logical cell counts differ; appending a column with targetIndex = columnCount (should be tableRows[0].getChildren().length - 1 or insertion at the end via a different path); calling on a table mutated by another plugin.","solutions":["Before calling, verify targetIndex < Math.min(...rows.map(r => r.getChildren().length)) and targetIndex >= 0.","Prefer $insertTableColumn__EXPERIMENTAL(), which uses $computeTableMap and handles colspans/rowspans correctly.","Clamp or recompute targetIndex from actual TableCellNode children rather than the DOM column index.","Normalize tables with merged cells (convert colspan/rowspan into explicit cells) before using the non-experimental helper."],"exampleFix":"// before\n$insertTableColumn(tableNode, columnCount, true, 1, table);\n// after\nconst minCells = Math.min(...tableNode.getChildren()\n  .filter($isTableRowNode)\n  .map(r => r.getChildren().length));\nif (targetIndex < 0 || targetIndex >= minCells) return;\n$insertTableColumn(tableNode, targetIndex, true, 1, table);","handlingStrategy":"validation","validationCode":"function canInsertColumn(tableNode, targetIndex) {\n  if (targetIndex < 0) return false;\n  const rows = tableNode.getChildren().filter($isTableRowNode);\n  return rows.every((r) => targetIndex < r.getChildren().length);\n}","typeGuard":"function isValidColumnIndex(row, i) {\n  return Number.isInteger(i) && i >= 0 && i < row.getChildren().length;\n}","tryCatchPattern":"try {\n  editor.update(() => $insertTableColumn(tableNode, targetIndex, true, 1, table));\n} catch (e) {\n  if (e.message === 'Table column target index out of range') {\n    // fall back to experimental API which handles colspans\n    editor.update(() => $insertTableColumn__EXPERIMENTAL());\n  } else throw e;\n}","preventionTips":["Never derive the target index from the visual DOM column count when colspan/rowspan cells exist.","Prefer $insertTableColumn__EXPERIMENTAL() which uses the computed table map.","Clamp the index against the minimum per-row cell count before every call."],"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"}