{"record":{"id":"cffa6a62c2a01508","repo":"PHPOffice/PhpSpreadsheet","slug":"rows-to-be-deleted-should-at-least-start-from-row","errorCode":null,"errorMessage":"Rows to be deleted should at least start from row 1.","messagePattern":"Rows to be deleted should at least start from row 1\\.","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Worksheet/Worksheet.php","lineNumber":2596,"sourceCode":"        if ($beforeColumnIndex >= 1) {\n            return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $numberOfColumns);\n        }\n\n        throw new Exception('Columns can only be inserted before at least column A (1).');\n    }\n\n    /**\n     * Delete a row, updating all possible related data.\n     *\n     * @param int $row Remove rows, starting with this row number\n     * @param int $numberOfRows Number of rows to remove\n     *\n     * @return $this\n     */\n    public function removeRow(int $row, int $numberOfRows = 1): static\n    {\n        if ($row < 1) {\n            throw new Exception('Rows to be deleted should at least start from row 1.');\n        }\n        if ($numberOfRows === 0) {\n            return $this;\n        }\n        if ($numberOfRows < 0) {\n            $newRow = max(1, $row + $numberOfRows + 1);\n            $numberOfRows = $row - $newRow + 1;\n            $row = $newRow;\n        }\n        $newHighestRow = $this->cachedHighestRow;\n        if ($newHighestRow >= $row) {\n            $newHighestRow = max($row - 1, $this->cachedHighestRow - $numberOfRows);\n        }\n        $startRow = $row;\n        $endRow = $startRow + $numberOfRows - 1;\n        $removeKeys = [];\n        $addKeys = [];\n        foreach ($this->mergeCells as $key => $value) {","sourceCodeStart":2578,"sourceCodeEnd":2614,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Worksheet/Worksheet.php#L2578-L2614","documentation":"removeRow(int $row, int $numberOfRows = 1) deletes rows starting at $row. The start row must be >= 1; negative $numberOfRows values are normalized internally (removeRow(5, -2) removes rows 3-4), but a start row below 1 always throws before any shifting happens.","triggerScenarios":"removeRow(0) or removeRow(-2); header math like removeRow($firstDataRow - 1) evaluating to 0 when data starts at row 1; passing row numbers taken from 0-based arrays or parsed CSV offsets.","commonSituations":"Deleting everything above a data block but computing the start from a 0-based offset; importers feeding array indices to the API; using the negative-count convenience with a bad start row.","solutions":["Pass a start row >= 1; row 1 is the first spreadsheet row.","To remove the first N rows including headers, use removeRow(1, $n).","Clamp computed starts: $row = max(1, $computed);","When using negative $numberOfRows (e.g. removeRow(5, -2)), verify the normalized start still lands at or above row 1."],"exampleFix":"// before\n$dataStart = 0; // 0-based header offset\n$sheet->removeRow($dataStart, 1); // throws\n\n// after\n$sheet->removeRow(1, 1); // delete row 1 explicitly\n// or delete rows 1-3 (header + 2 data rows):\n$sheet->removeRow(1, 3);","handlingStrategy":"validation","validationCode":"$row = $firstContentRow; // whatever the data says\nif ($row < 1) {\n    $row = 1;\n}\n$sheet->removeRow($row, 2);","typeGuard":"/** Spreadsheet rows are 1-based: row 1 is the first row. */\nfunction isPositiveRow(int $row): bool\n{\n    return $row >= 1;\n}","tryCatchPattern":null,"preventionTips":["To delete from the top of the sheet, always start at removeRow(1, $n).","Clamp computed start rows with max(1, $row).","Remember negative $numberOfRows re-bases the start row; verify it stays >= 1."],"tags":["phpspreadsheet","worksheet","remove-row","row-index","argument-validation"],"backgroundTag":"off-by-one-index","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}