BookStackApp/BookStack · error · Error

Row before insertion index does not exist.

Error message

Row before insertion index does not exist.

What it means

$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.

Source

Thrown at resources/js/wysiwyg/lexical/table/LexicalTableUtils.ts:230

        ) {
          headerState |= TableCellHeaderStates.COLUMN;
        }

        const tableCellNode = $createTableCellNode(headerState, 1, width);

        tableCellNode.append($createParagraphNode());

        newTableRowNode.append(tableCellNode);
      }

      if (shouldInsertAfter) {
        targetRowNode.insertAfter(newTableRowNode);
      } else {
        targetRowNode.insertBefore(newTableRowNode);
      }
    }
  } else {
    throw new Error('Row before insertion index does not exist.');
  }

  return tableNode;
}

const getHeaderState = (
  currentState: TableCellHeaderState,
  possibleState: TableCellHeaderState,
): TableCellHeaderState => {
  if (
    currentState === TableCellHeaderStates.BOTH ||
    currentState === possibleState
  ) {
    return possibleState;
  }
  return TableCellHeaderStates.NO_STATUS;
};

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Validate table children before inserting: every child of the TableNode should pass $isTableRowNode; remove or normalize any non-row child.
  2. Prefer $insertTableRow__EXPERIMENTAL() which derives the target row from the current selection instead of a hand-computed index.
  3. Recompute targetIndex from the live TableNode (tableNode.getChildren()) immediately before the call rather than caching it.
  4. Wrap the call in try/catch inside your update and surface a user-facing message instead of crashing the editor transaction.

Example fix

// before
const rows = tableNode.getChildren();
$insertTableRow(tableNode, rows.length - 1, true, 1, table);
// after
const rows = tableNode.getChildren();
const targetIndex = rows.findIndex($isTableRowNode);
if (targetIndex === -1) return;
$insertTableRow(tableNode, targetIndex, true, 1, table);
Defensive patterns

Strategy: validation

Validate before calling

function canInsertRow(tableNode) {
  const rows = tableNode.getChildren();
  return rows.length > 0 && rows.every($isTableRowNode);
}

Type guard

function isInsertableTable(node) {
  return $isTableNode(node) && node.getChildren().every((c) => $isTableRowNode(c));
}

Try / catch

try {
  editor.update(() => $insertTableRow(tableNode, targetIndex, true, 1, table));
} catch (e) {
  if (e.message === 'Row before insertion index does not exist.') {
    // log and skip; table structure is not row-pure
  } else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/97b2544b6b9b9f6c. Report an issue: GitHub.