Creating and manipulating Excel spreadsheets programmatically is a common requirement in web applications. Whether you need to export data from a database, generate reports, or allow users to download formatted files, PHP provides powerful libraries for working with Excel files. This comprehensive guide covers everything you need to know about creating and editing Excel spreadsheets using PhpSpreadsheet, the modern successor to the deprecated PHPExcel library.
PhpSpreadsheet is a pure PHP library that allows you to read and write different spreadsheet file formats without requiring Microsoft Office or any external dependencies. It’s the official successor to PHPExcel, which was deprecated in 2017 and permanently archived in 2019.

PHPExcel, while once the industry standard, is no longer maintained and poses security risks. PhpSpreadsheet improves upon its predecessor by introducing:
All existing PHPExcel code can be migrated to PhpSpreadsheet, though it requires updating class names and namespaces.
PhpSpreadsheet supports reading and writing multiple spreadsheet formats:
| Format | Extension | Read | Write |
| Microsoft Excel 2007+ | .xlsx | ✓ | ✓ |
| Microsoft Excel 97-2003 | .xls | ✓ | ✓ |
| OpenDocument Format | .ods | ✓ | ✓ |
| Comma-Separated Values | .csv | ✓ | ✓ |
This flexibility allows you to work with data from almost any spreadsheet source and export to your users’ preferred format.
Before installing PhpSpreadsheet, ensure you have:
The recommended way to install PhpSpreadsheet is through Composer:
composer require phpoffice/phpspreadsheet
This command downloads PhpSpreadsheet and all its dependencies into a vendor directory and creates a composer.lock file to track versions.
After installation, your project structure should look like:
your-project/
├── vendor/
│ └── phpoffice/
│ └── phpspreadsheet/
├── composer.json
├── composer.lock
└── your-script.php
In your PHP files, include Composer’s autoloader to access PhpSpreadsheet classes:
php
<?php
require_once 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
?>
Here’s the simplest way to create an Excel file:
<?php
require_once 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
// Create a new spreadsheet object
$spreadsheet = new Spreadsheet();
// Get the active worksheet (Sheet1 by default)
$sheet = $spreadsheet->getActiveSheet();
// Set the sheet title
$sheet->setTitle('My First Sheet');
// Add data to cells
$sheet->setCellValue('A1', 'Hello');
$sheet->setCellValue('B1', 'World');
$sheet->setCellValue('A2', 'Product Name');
$sheet->setCellValue('B2', 'Price');
$sheet->setCellValue('A3', 'Laptop');
$sheet->setCellValue('B3', 999.99);
// Create an Excel file writer
$writer = new Xlsx($spreadsheet);
// Save the file
$writer->save('output.xlsx');
echo "File created successfully!";
?>
PhpSpreadsheet uses Excel’s cell reference system. A column letter and row number identify each cell:
You can set cell values using:
$sheet->setCellValue('A1', 'Value'); // Direct reference
$sheet->setCellValueByColumnAndRow(1, 1, 'Value'); // Column index (1=A, 2=B) and row
Professional spreadsheets include metadata that identifies the document:
<?php
// Set spreadsheet properties
$properties = $spreadsheet->getProperties();
$properties->setCreator('Your Name');
$properties->setLastModifiedBy('Your Name');
$properties->setTitle('Sales Report Q4 2024');
$properties->setSubject('Quarterly Sales Analysis');
$properties->setDescription('Complete sales data for Q4 2024 including all regions');
$properties->setKeywords('sales, q4, report');
$properties->setCategory('Business Reports');
?>
These properties appear when users open the file’s “Properties” dialog in Excel and help with document organization.
Creating visually appealing and readable spreadsheets involves formatting cells, text, and layouts.
php
<?php
use PhpOffice\PhpSpreadsheet\Style\Font;
// Set font for a single cell
$sheet->getStyle('A1')->getFont()
->setName('Arial')
->setSize(14)
->setBold(true)
->setItalic(false)
->setUnderline(Font::UNDERLINE_SINGLE)
->setColor(new Color('FF0000')); // Red color
// Apply the same font to a range of cells
$sheet->getStyle('A1:D1')->getFont()
->setName('Arial')
->setSize(12)
->setBold(true);
?>
<?php
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Color;
// Set cell background color
$sheet->getStyle('A1')->getFill()
->setFillType(Fill::FILL_SOLID)
->getStartColor()->setARGB('FFFF0000'); // Red background
// Apply to a range
$sheet->getStyle('A1:D1')->getFill()
->setFillType(Fill::FILL_SOLID)
->getStartColor()->setARGB('FFCCCCCC'); // Light gray background
?>
<?php
use PhpOffice\PhpSpreadsheet\Style\Border;
$styleArray = [
'borders' => [
'allborders' => [
'borderStyle' => Border::BORDER_THIN,
'color' => ['argb' => 'FF000000'], // Black borders
],
],
];
$sheet->getStyle('A1:D5')->applyFromArray($styleArray);
// Available border styles:
// Border::BORDER_NONE, BORDER_THIN, BORDER_MEDIUM, BORDER_THICK
// BORDER_DOUBLE, BORDER_DOTTED, BORDER_DASHED, BORDER_HAIR
?>
<?php
use PhpOffice\PhpSpreadsheet\Style\Alignment;
$sheet->getStyle('A1')->getAlignment()
->setHorizontal(Alignment::HORIZONTAL_CENTER)
->setVertical(Alignment::VERTICAL_CENTER)
->setWrapText(true);
// Available alignment options:
// HORIZONTAL: LEFT, CENTER, RIGHT, DISTRIBUTED, JUSTIFY
// VERTICAL: TOP, CENTER, BOTTOM, DISTRIBUTED, JUSTIFY
?>
php
<?php
// Set specific column width (in pixels)
$sheet->getColumnDimension('A')->setWidth(20);
$sheet->getColumnDimension('B')->setWidth(30);
// Auto-fit column width based on content
$sheet->getColumnDimension('A')->setAutoSize(true);
// Set width for multiple columns
for ($col = 'A'; $col != 'F'; $col++) {
$sheet->getColumnDimension($col)->setAutoSize(true);
}
?>
php
<?php
// Set specific row height
$sheet->getRowDimension(1)->setHeight(25);
// Auto-fit row height based on content
$sheet->getRowDimension(1)->setRowHeight(null); // null triggers auto-calculation
?>
<?php
// Insert 3 new rows before row 5
$sheet->insertNewRowBefore(5, 3);
// Insert 2 new columns before column C
$sheet->insertNewColumnBefore('C', 2);
// Remove rows 2 through 4
$sheet->removeRow(2, 3);
// Remove column B
$sheet->removeColumn('B');
?>
Merging cells creates a single large cell from multiple cells:
<?php
// Merge cells A1 through D1
$sheet->mergeCells('A1:D1');
// Set content for merged cell (set in top-left cell)
$sheet->setCellValue('A1', 'Merged Header');
// Unmerge cells
$sheet->unmergeCells('A1:D1');
?>
Freezing panes keeps header rows or columns visible when scrolling:
<?php
// Freeze the first row (data starts in row 2)
$sheet->freezePane('A2');
// Freeze first row and first column (data starts in B2)
$sheet->freezePane('B2');
// Freeze first three rows
$sheet->freezePane('A4');
// Unfreeze all panes
$sheet->unfreezePane();
?>
PhpSpreadsheet supports Excel formulas, which are evaluated when the file is opened:
<?php
// Add a SUM formula
$sheet->setCellValue('A1', 10);
$sheet->setCellValue('A2', 20);
$sheet->setCellValue('A3', 30);
$sheet->setCellValue('A4', '=SUM(A1:A3)'); // Result: 60
// Add an AVERAGE formula
$sheet->setCellValue('B4', '=AVERAGE(A1:A3)'); // Result: 20
// Add IF formula
$sheet->setCellValue('C1', '=IF(A1>15,"High","Low")');
// Add CONCATENATE formula
$sheet->setCellValue('D1', '=CONCATENATE("Hello ",B1)');
// Get calculated value (requires calculation engine enabled)
$value = $sheet->getCell('A4')->getCalculatedValue(); // 60
?>
PhpSpreadsheet supports most Excel functions, including:
Add dropdown lists to cells for data consistency:
<?php
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
$dataValidation = new DataValidation();
$dataValidation->setType(DataValidation::TYPE_LIST);
$dataValidation->setErrorStyle(DataValidation::STYLE_INFORMATION);
$dataValidation->setAllowBlank(false);
$dataValidation->setShowInputMessage(true);
$dataValidation->setShowErrorMessage(true);
$dataValidation->setShowDropDown(true);
$dataValidation->setErrorTitle('Input Error');
$dataValidation->setError('Please select from the list');
$dataValidation->setPromptTitle('Select a value');
$dataValidation->setPrompt('Choose from the dropdown list');
// Set the dropdown values (comma-separated list)
$dataValidation->setFormula1('"Red,Green,Blue,Yellow"');
// Add validation to specific cell
$sheet->getCell('A1')->setDataValidation($dataValidation);
?>
Excel workbooks can contain multiple sheets:
<?php
// Get the default active sheet
$sheet1 = $spreadsheet->getActiveSheet();
$sheet1->setTitle('Sales Data');
// Create a new sheet
$sheet2 = $spreadsheet->createSheet();
$sheet2->setTitle('Expenses');
// Create another sheet with specific index position
$sheet3 = $spreadsheet->createSheet();
$sheet3->setTitle('Summary');
// Switch to a specific sheet
$spreadsheet->setActiveSheetIndex(0); // Activate first sheet
// Access sheets by name
$expensesSheet = $spreadsheet->getSheetByName('Expenses');
// Get sheet count
$sheetCount = $spreadsheet->getSheetCount(); // Returns 3
// Get all sheet names
$sheetNames = $spreadsheet->getSheetNames(); // Returns array of sheet names
// Delete a sheet
$spreadsheet->removeSheetByIndex(2); // Remove third sheet
?>
<?php
use PhpOffice\PhpSpreadsheet\IOFactory;
// Automatically detect file format (XLSX, XLS, CSV, ODS)
$spreadsheet = IOFactory::load('myfile.xlsx');
// Or specify the format explicitly
$reader = new XlsxReader();
$spreadsheet = $reader->load('myfile.xlsx');
// Get active sheet
$sheet = $spreadsheet->getActiveSheet();
// Get cell value
$value = $sheet->getCell('A1')->getValue();
// Get calculated value (if cell contains formula)
$value = $sheet->getCell('A4')->getCalculatedValue();
?>
php
<?php
// Convert entire sheet to array
$data = $sheet->toArray();
// Convert with specified parameters
$data = $sheet->toArray(
null, // Value from cells, null means use cell value
true, // Calculate formulas
true, // Format cells
true // Use cell references as array keys
);
// Iterate through rows
foreach ($sheet->getRowIterator() as $row) {
$cellIterator = $row->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(false);
foreach ($cellIterator as $cell) {
echo $cell->getValue();
}
}
?>
php
<?php
use PhpOffice\PhpSpreadsheet\Reader\Csv;
$reader = new Csv();
$reader->setDelimiter(';'); // Delimiter (comma by default)
$reader->setEnclosure('"'); // Field enclosure character
$reader->setInputEncoding('UTF-8'); // File encoding
$spreadsheet = $reader->load('data.csv');
?>
<?php
// Load an existing file
$spreadsheet = IOFactory::load('products.xlsx');
// Get specific sheet
$sheet = $spreadsheet->getActiveSheet();
// Modify data
$sheet->setCellValue('A1', 'Updated Value');
// Remove specific rows
$sheet->removeRow(2, 2); // Remove 2 rows starting at row 2
// Insert new rows
$sheet->insertNewRowBefore(2, 1); // Insert 1 row before row 2
// Save changes to new file
$writer = new Xlsx($spreadsheet);
$writer->save('updated_products.xlsx');
// Or overwrite original
$writer->save('products.xlsx');
?>
Instead of saving to server storage, send files directly to users’ browsers:
php
<?php
// Set appropriate headers for file download
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="report.xlsx"');
header('Cache-Control: max-age=0');
// Write to output stream instead of file
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
exit;
?>
php
<?php
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use PhpOffice\PhpSpreadsheet\Writer\Xls;
// Download as CSV
header('Content-Type: text/csv');
header('Content-Disposition: attachment;filename="data.csv"');
$writer = new Csv($spreadsheet);
$writer->save('php://output');
// Download as XLS (Excel 97-2003)
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="data.xls"');
$writer = new Xls($spreadsheet);
$writer->save('php://output');
// Download as XLSX (Excel 2007+)
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="data.xlsx"');
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
?>
<?php
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
$drawing = new Drawing();
$drawing->setName('Product Logo');
$drawing->setDescription('Company Logo');
$drawing->setPath('/path/to/image.jpg'); // Local file
$drawing->setHeight(100); // Height in pixels
$drawing->setWidth(100); // Width in pixels
$drawing->setCoordinates('A1'); // Cell position
$drawing->setWorksheet($sheet);
?>
php
<?php
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
// Create chart data
$categories = new DataSeriesValues(
DataSeriesValues::DATASERIES_TYPE_STRING,
'Sheet1!$A$2:$A$4',
null,
3
);
$values = new DataSeriesValues(
DataSeriesValues::DATASERIES_TYPE_NUMBER,
'Sheet1!$B$2:$B$4',
null,
3
);
// Create data series
$series = new DataSeries(
DataSeries::TYPE_BARCHART,
null,
range(0, 0),
[$categories],
[$values]
);
// Create chart
$chart = new Chart('chart1', null, null, new PlotArea(null, [$series]));
$chart->setTopLeftPosition('D2');
$chart->setBottomRightPosition('H15');
$sheet->addChart($chart);
?>
<?php
// Add autofilter to a range of data
$sheet->setAutoFilter('A1:D10'); // Makes first row filterable dropdown menu
?>
php
<?php
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
$conditionalRule = new Wizard('A1:A10');
$conditionalRule = $conditionalRule
->newRule(Wizard::CELL_VALUE)
->greaterThan(100);
$style = $conditionalRule->getStyle();
$style->getFont()->getColor()->setARGB('FFFF0000'); // Red text
$style->getFill()->setFillType(Fill::FILL_SOLID)->getStartColor()->setARGB('FFFFFF00'); // Yellow background
$conditionalStyles = $sheet->getStyle('A1:A10')->getConditionalStyles();
$conditionalStyles[] = $conditionalRule->getConditional();
$sheet->getStyle('A1:A10')->setConditionalStyles($conditionalStyles);
?>
Configure how your spreadsheet will appear when printed:
<?php
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
use PhpOffice\PhpSpreadsheet\Worksheet\PageMargins;
// Set page orientation
$sheet->getPageSetup()
->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
// Or PageSetup::ORIENTATION_PORTRAIT
// Set paper size
$sheet->getPageSetup()
->setPaperSize(PageSetup::PAPERSIZE_A4);
// Other options: PAPERSIZE_LETTER, PAPERSIZE_A3, etc.
// Set margins (in inches)
$sheet->getPageMargins()
->setTop(1)
->setRight(0.75)
->setBottom(1)
->setLeft(0.75);
// Set print area (only print specific range)
$sheet->getPageSetup()->setPrintArea('A1:D10');
// Set print titles (repeat header rows on each page)
$sheet->getPageSetup()->setRowsToRepeatAtTopByStartAndEndRow(1, 1);
?>
When working with large spreadsheets, memory usage becomes critical:
<?php
// Use read data only mode (faster, less memory)
$reader = new XlsxReader();
$reader->setReadDataOnly(true); // Ignores formatting, formulas
$spreadsheet = $reader->load('largefile.xlsx');
// Use chunking to process large datasets
$reader->setReadFilter(new MyReadFilter()); // Custom read filter
// For very large files, consider using streaming libraries like Spout
// Spout is optimized for memory-efficient reading/writing of large files
?>
<?php
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Add header row
$sheet->setCellValue('A1', 'ID');
$sheet->setCellValue('B1', 'Product Name');
$sheet->setCellValue('C1', 'Price');
$sheet->setCellValue('D1', 'Quantity');
// Style header
$sheet->getStyle('A1:D1')->getFont()->setBold(true);
// Get data from database
$result = mysqli_query($connection, "SELECT id, name, price, quantity FROM products");
$row = 2;
while ($product = mysqli_fetch_assoc($result)) {
$sheet->setCellValue('A' . $row, $product['id']);
$sheet->setCellValue('B' . $row, $product['name']);
$sheet->setCellValue('C' . $row, $product['price']);
$sheet->setCellValue('D' . $row, $product['quantity']);
$row++;
}
// Auto-fit columns
$sheet->getColumnDimension('A')->setAutoSize(true);
$sheet->getColumnDimension('B')->setAutoSize(true);
$sheet->getColumnDimension('C')->setAutoSize(true);
$sheet->getColumnDimension('D')->setAutoSize(true);
// Download
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="products.xlsx"');
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
?>
php
<?php
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// Title
$sheet->setCellValue('A1', 'INVOICE');
$sheet->getStyle('A1')->getFont()->setSize(16)->setBold(true);
// Invoice details
$sheet->setCellValue('A3', 'Invoice Number:');
$sheet->setCellValue('B3', 'INV-2024-001');
$sheet->setCellValue('A4', 'Date:');
$sheet->setCellValue('B4', date('Y-m-d'));
// Line items table
$sheet->setCellValue('A7', 'Description');
$sheet->setCellValue('B7', 'Quantity');
$sheet->setCellValue('C7', 'Unit Price');
$sheet->setCellValue('D7', 'Total');
$sheet->getStyle('A7:D7')->getFont()->setBold(true)->setUnderline(true);
// Sample line items
$sheet->setCellValue('A8', 'Product A');
$sheet->setCellValue('B8', 5);
$sheet->setCellValue('C8', 100);
$sheet->setCellValue('D8', '=B8*C8');
$sheet->setCellValue('A9', 'Product B');
$sheet->setCellValue('B9', 3);
$sheet->setCellValue('C9', 150);
$sheet->setCellValue('D9', '=B9*C9');
// Totals
$sheet->setCellValue('C11', 'Subtotal:');
$sheet->setCellValue('D11', '=SUM(D8:D9)');
$sheet->setCellValue('C12', 'Tax (10%):');
$sheet->setCellValue('D12', '=D11*0.10');
$sheet->setCellValue('C13', 'Total:');
$sheet->setCellValue('D13', '=D11+D12');
// Format currency
$sheet->getStyle('D8:D13')->getNumberFormat()->setFormatCode('$#,##0.00');
// Download
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="invoice.xlsx"');
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
?>
Problem: Working with large Excel files can cause “allowed memory size exhausted” misconceptions.
Solution:
Problem: Non-ASCII characters appear as garbled text.
Solution:
Problem: Formulas display as text instead of results.
Solution:
Problem: Writing thousands of rows takes a very long time.
Solution:
PhpSpreadsheet is a powerful and flexible library that transforms how you handle Excel spreadsheets in PHP applications. From simple data exports to complex multi-sheet workbooks with formulas and formatting, it provides the tools necessary for professional spreadsheet management.
By understanding the key concepts covered in this guide, creating and modifying spreadsheets, styling cells, working with formulas, and handling multiple sheets, you can implement sophisticated spreadsheet functionality in your web applications. Whether you’re generating reports, creating invoices, or allowing users to download formatted data, PhpSpreadsheet provides a robust foundation for your needs.

Hassan Tahir wrote this article, drawing on his experience to clarify WordPress concepts and enhance developer understanding. Through his work, he aims to help both beginners and professionals refine their skills and tackle WordPress projects with greater confidence.