PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception

Cell range {$range} not known as protected.

Error message

Cell range {$range} not known as protected.

What it means

Worksheet::unprotectCells() checks the exact normalized range string against the sheet's protectedCells map and throws when it is not a key. The input goes through trimSheetFromCellReference + validateCellOrCellRange (which expand a single cell to 'A1:A1'), but the resulting string must still equal the exact range used when protectCells() registered it.

Source

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

    }

    /**
     * Remove protection on a cell or cell range.
     *
     * @param AddressRange<CellAddress>|AddressRange<int>|AddressRange<string>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10'
     *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
     *              or a CellAddress or AddressRange object.
     *
     * @return $this
     */
    public function unprotectCells(AddressRange|CellAddress|int|string|array $range): static
    {
        $range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range));

        if (isset($this->protectedCells[$range])) {
            unset($this->protectedCells[$range]);
        } else {
            throw new Exception('Cell range ' . $range . ' not known as protected.');
        }

        return $this;
    }

    /**
     * Get protected cells.
     *
     * @return ProtectedRange[]
     */
    public function getProtectedCellRanges(): array
    {
        return $this->protectedCells;
    }

    /**
     * Get Autofilter.
     */

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Enumerate the real entries via $sheet->getProtectedCellRanges() (returns ProtectedRange objects — use their range string, e.g. $pr->getRange()) and unprotect exactly that
  2. Guard before the call: in_array($range, array_keys-ish list, true) or inspect getProtectedCellRanges() names/ranges
  3. If nothing is protected, skip the call rather than unprotecting speculatively

Example fix

// before
$sheet->unprotectCells('A1:B10'); // actual protected range was 'A1:B5' -> throws

// after
foreach ($sheet->getProtectedCellRanges() as $protectedRange) {
    $sheet->unprotectCells($protectedRange->getRange()); // exact stored string
}
Defensive patterns

Strategy: validation

Validate before calling

$known = array_map(
    fn (ProtectedRange $pr) => $pr->getRange(),
    $sheet->getProtectedCellRanges()
);
if (in_array($range, $known, true)) {
    $sheet->unprotectCells($range);
}

Try / catch

try {
    $sheet->unprotectCells($range);
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
    if (str_contains($e->getMessage(), 'not known as protected')) {
        // nothing was protected under that exact range — skip
    } else {
        throw $e;
    }
}

Prevention

When it happens

Trigger: unprotectCells('A1:B10') when protection was registered as 'A1:B5' or never at all; unprotecting a differently formatted string (e.g. with '$' signs or lowercase) that does not match the stored key; calling unprotect after a fresh load where the protection lives on another sheet.

Common situations: Tracking protection ranges in your own DB/schema whose formatting drifts from what protectCells() stored; unprotecting guessed boundaries; sheet-level protection vs cell-range protection being confused.

Related errors


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