PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception

Merge must be on a valid range of cells.

Error message

Merge must be on a valid range of cells.

What it means

Worksheet::mergeCells() normalizes its input (sheet-name prefix trimmed, single cell expanded to 'X:Y') and then requires the result to match ^([A-Z]+)(\d+):([A-Z]+)(\d+)$ — uppercase letters, then digits, on both sides. Anything else (lowercase column letters, non-coordinate text, malformed addresses) fails the regex and throws.

Source

Thrown at src/PhpSpreadsheet/Worksheet/Worksheet.php:1851

     *              or an AddressRange.
     * @param string $behaviour How the merged cells should behave.
     *               Possible values are:
     *                   MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells
     *                   MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells
     *                   MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell
     *
     * @return $this
     */
    public function mergeCells(AddressRange|string|array $range, string $behaviour = self::MERGE_CELL_CONTENT_EMPTY): static
    {
        $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range));

        if (!str_contains($range, ':')) {
            $range .= ":{$range}";
        }

        if (!Preg::isMatch('/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/', $range, $matches)) {
            throw new Exception('Merge must be on a valid range of cells.');
        }

        $this->mergeCells[$range] = $range;
        $firstRow = (int) $matches[2];
        $lastRow = (int) $matches[4];
        $firstColumn = $matches[1];
        $lastColumn = $matches[3];
        $firstColumnIndex = Coordinate::columnIndexFromString($firstColumn);
        $lastColumnIndex = Coordinate::columnIndexFromString($lastColumn);
        $numberRows = $lastRow - $firstRow;
        $numberColumns = $lastColumnIndex - $firstColumnIndex;

        if ($numberRows === 1 && $numberColumns === 1) {
            return $this;
        }

        // create upper left cell if it does not already exist
        $upperLeft = "{$firstColumn}{$firstRow}";

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Normalize strings before merging: $range = strtoupper(trim($range)); then mergeCells($range)
  2. Build the range from trusted parts: Coordinate::stringFromColumnIndex($c) . $row . ':' . ...
  3. Prefer the structured forms — mergeCells([fromCol, fromRow, toCol, toRow]) or a CellRange — which are validated/normalized for you

Example fix

// before
$sheet->mergeCells('c5:f8'); // lowercase -> regex fails

// after
$sheet->mergeCells(strtoupper('c5:f8')); // 'C5:F8'
// or structured:
$sheet->mergeCells([3, 5, 6, 8]);
Defensive patterns

Strategy: validation

Validate before calling

$range = strtoupper(trim($range));
if (preg_match('/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/', $range) === 1) {
    $sheet->mergeCells($range);
} else {
    throw new InvalidArgumentException("Invalid merge range '$range'");
}

Prevention

When it happens

Trigger: $sheet->mergeCells('a1:b2') — lowercase letters fail the regex; mergeCells('total'); mergeCells('A-1:B2'); values concatenated with stray characters ('A1 :B2'). The array/CellRange forms are normalized by Validations, so this mainly bites string input.

Common situations: Passing user-typed or external-system range strings directly (often lowercase); ranges assembled from unchecked variables where a piece is empty or non-numeric.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/0ace3a7d1dea9510. Report an issue: GitHub.