PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Exception
Workbook already contains a worksheet named '{$worksheet->ge
Error message
Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename the external sheet first. What it means
Spreadsheet::addExternalSheet() moves a worksheet that belongs to another workbook into this one, copying the external workbook's shared cellXf styles along with it. Unlike addSheet() there is no auto-rename path: if this workbook already has a sheet with the incoming title, it throws immediately and tells you to rename the external sheet first.
Source
Thrown at src/PhpSpreadsheet/Spreadsheet.php:871
$returnValue = [];
$worksheetCount = $this->getSheetCount();
for ($i = 0; $i < $worksheetCount; ++$i) {
$returnValue[] = $this->getSheet($i)->getTitle();
}
return $returnValue;
}
/**
* Add external sheet.
*
* @param Worksheet $worksheet External sheet to add
* @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
*/
public function addExternalSheet(Worksheet $worksheet, ?int $sheetIndex = null): Worksheet
{
if ($this->sheetNameExists($worksheet->getTitle())) {
throw new Exception("Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename the external sheet first.");
}
// count how many cellXfs there are in this workbook currently, we will need this below
$countCellXfs = count($this->cellXfCollection);
// copy all the shared cellXfs from the external workbook and append them to the current
foreach ($worksheet->getParentOrThrow()->getCellXfCollection() as $cellXf) {
$this->addCellXf(clone $cellXf);
}
// move sheet to this workbook
$worksheet->rebindParent($this);
// update the cellXfs
foreach ($worksheet->getCoordinates(false) as $coordinate) {
$cell = $worksheet->getCell($coordinate);
$cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
}View on GitHub (pinned to 65b080eef4)
Solutions
- Rename before moving: $sheet->setTitle('Source - ' . $sheet->getTitle()); then addExternalSheet($sheet).
- Or rename the collision target in the destination workbook to free the name.
- Or drop the existing destination sheet first: $target->removeSheetByIndex($target->getIndex($target->getSheetByName($name)));
- Unify names with a prefix/suffix scheme (source file basename, date) so merges never collide.
Example fix
// before
$target->addExternalSheet($source->getSheetByNameOrThrow('Sheet1')); // throws
// after
$sheet = $source->getSheetByNameOrThrow('Sheet1');
if ($target->sheetNameExists($sheet->getTitle())) {
$sheet->setTitle(pathinfo($sourcePath, PATHINFO_FILENAME) . ' - ' . $sheet->getTitle());
}
$target->addExternalSheet($sheet); Defensive patterns
Strategy: validation
Validate before calling
if ($target->sheetNameExists($sheet->getTitle())) {
$sheet->setTitle($prefix . ' - ' . $sheet->getTitle());
}
$target->addExternalSheet($sheet); Type guard
function externalSheetTitleIsFree(\PhpOffice\PhpSpreadsheet\Spreadsheet $target, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $w): bool
{
return !$target->sheetNameExists($w->getTitle());
} Try / catch
try {
$target->addExternalSheet($sheet);
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
$sheet->setTitle(uniqid('sheet-'));
$target->addExternalSheet($sheet);
} Prevention
- Prefix merged sheet titles with the source file/date by convention.
- Check sheetNameExists() on the target before every merge.
- Centralize merge logic in one helper that always uniquifies.
When it happens
Trigger: Merging two workbooks whose sheets share names (both contain 'Sheet1'): $target->addExternalSheet($source->getSheetByNameOrThrow('Sheet1')); importing a generated 'Data' tab into a report that already has 'Data'; consolidating monthly files that all use the same tab name.
Common situations: Report consolidation pipelines (many source files, one destination) without a per-source prefix/suffix on tab names; combining a template workbook with uploaded workbooks where users keep default names; merging after copying sheets with Worksheet::copy().
Related errors
- Workbook already contains a worksheet named '{$worksheet->ge
- Cannot update when cell is not bound to a worksheet
- Cannot get column when cell is not bound to a worksheet
- Cannot get row when cell is not bound to a worksheet
- Cannot check for data validation when cell is not bound to a
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/5cf9c673ed47112d.
Report an issue: GitHub.