{"record":{"id":"57b03c4355ad4ddb","repo":"PHPOffice/PhpSpreadsheet","slug":"cell-range-range-not-known-as-merged","errorCode":null,"errorMessage":"Cell range {$range} not known as merged.","messagePattern":"Cell range (.+?) not known as merged\\.","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Worksheet/Worksheet.php","lineNumber":1973,"sourceCode":"\n    /**\n     * Remove merge on a cell range.\n     *\n     * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range A simple string containing a Cell range like 'A1:E10'\n     *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),\n     *              or an AddressRange.\n     *\n     * @return $this\n     */\n    public function unmergeCells(AddressRange|string|array $range): static\n    {\n        $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range));\n\n        if (str_contains($range, ':')) {\n            if (isset($this->mergeCells[$range])) {\n                unset($this->mergeCells[$range]);\n            } else {\n                throw new Exception('Cell range ' . $range . ' not known as merged.');\n            }\n        } else {\n            throw new Exception('Merge can only be removed from a range of cells.');\n        }\n\n        return $this;\n    }\n\n    /**\n     * Get merge cells array.\n     *\n     * @return string[]\n     */\n    public function getMergeCells(): array\n    {\n        return $this->mergeCells;\n    }\n","sourceCodeStart":1955,"sourceCodeEnd":1991,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Worksheet/Worksheet.php#L1955-L1991","documentation":"Worksheet::unmergeCells() looks the exact normalized range string up in the sheet's merged-cells map and throws when it is absent. Unlike mergeCells() it does not normalize bounds or expand single cells, so the string must byte-for-byte equal a stored entry (which mergeCells stores in normalized 'A1:B2' form: colon present, top-left first).","triggerScenarios":"unmergeCells('A1:C3') when the actual merge is 'A1:B2'; unmerging before any mergeCells() call on that range; passing reversed bounds 'C3:A1' or a lowercase variant of a stored merge.","commonSituations":"Unmerging a guessed/remembered range instead of the exact one; loading third-party files where merge boundaries differ from expectations; unmerging a sub-block assumed to be independently merged.","solutions":["List the real merges first: $merges = $sheet->getMergeCells(); and pass one of those exact strings to unmergeCells()","To unmerge 'the block containing A1', find the containing entry: foreach ($sheet->getMergeCells() as $m) { if (Coordinate::coordinateIsInside? — check bounds) unmergeCells($m); } using rangeBoundaries containment","Guard with in_array($range, $sheet->getMergeCells(), true) before calling"],"exampleFix":"// before\n$sheet->unmergeCells('A1:C3'); // actual merge was 'A1:B2' -> throws\n\n// after\n$target = 'A1:C3';\nif (in_array($target, $sheet->getMergeCells(), true)) {\n    $sheet->unmergeCells($target);\n} else {\n    foreach ($sheet->getMergeCells() as $merge) {\n        if (str_contains($merge, 'A1:') || str_starts_with($merge, 'A1')) {\n            $sheet->unmergeCells($merge);\n        }\n    }\n}","handlingStrategy":"validation","validationCode":"if (in_array($range, $sheet->getMergeCells(), true)) {\n    $sheet->unmergeCells($range);\n} else {\n    // pick the exact stored string from getMergeCells() instead\n}","typeGuard":null,"tryCatchPattern":"try {\n    $sheet->unmergeCells($range);\n} catch (\\PhpOffice\\PhpSpreadsheet\\Exception $e) {\n    if (str_contains($e->getMessage(), 'not known as merged')) {\n        // range was never merged — nothing to do\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Always source the unmerge argument from getMergeCells(), never from memory","When unmerging 'the block containing a cell', find the containing stored range first","Remember unmergeCells does not normalize bounds — pass the stored string verbatim"],"tags":["phpspreadsheet","unmerge-cells","merge","state-mismatch","exact-match"],"backgroundTag":"invalid-state-transition","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}