How to read excel sheet using php
Reading Excel files in PHP comes up constantly in real projects — importing client data, processing accounting exports, building admin dashboards, or migrating spreadsheets to a database. Unlike CSV files, Excel files are binary (XLS) or zipped XML (XLSX) formats, so you can’t just open them with fopen(). You need a proper library.
This guide shows you how to read Excel files in PHP the modern, recommended way using PhpSpreadsheet — the actively maintained successor to the old PHPExcel library. By the end, you’ll know how to read XLSX and XLS files, grab specific cells, handle multiple worksheets, work with large files without crashing, and deal with the common quirks like merged cells and Excel date numbers.
Which PHP Library Should You Use to Read Excel?
There are a few options, but only one is genuinely recommended in 2026:
| Library | Status | Recommended? |
|---|---|---|
| PhpSpreadsheet | Actively maintained | ✅ Yes — use this |
| PHPExcel | Abandoned in 2017 | ❌ No — don’t use |
| Box/Spout | Archived in 2022 | ❌ No — unmaintained |
| OpenSpout | Active fork of Spout | ✅ Good for large-file streaming |
| simplexlsx | Active, lightweight | ✅ OK for simple read-only use |
PhpSpreadsheet is the go-to library — it reads and writes XLSX, XLS, CSV, ODS, and several other formats, and it’s used by virtually every major PHP framework and CMS that touches Excel. We’ll focus on it here.
Step 1: Install PhpSpreadsheet via Composer
You need Composer (PHP’s package manager) installed. From your project root:
composer require phpoffice/phpspreadsheet
This adds the library and all its dependencies. PhpSpreadsheet requires PHP 8.1 or newer as of the current version.
Then include Composer’s autoloader at the top of your PHP file:
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;
Step 2: Basic Example — Read an Excel File Into an Array
The simplest, most common task: load an XLSX file and convert it to a PHP array.
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;
$filePath = 'data.xlsx';
// Load the spreadsheet (auto-detects XLSX, XLS, CSV, ODS, etc.)
$spreadsheet = IOFactory::load($filePath);
// Get the first worksheet
$worksheet = $spreadsheet->getActiveSheet();
// Convert all cells to a 2D array
$rows = $worksheet->toArray(null, true, true, true);
foreach ($rows as $row) {
print_r($row);
}
What’s happening:
IOFactory::load($filePath)auto-detects the file format and returns aSpreadsheetobject.getActiveSheet()returns the currently selected worksheet (usually the first one).toArray(null, true, true, true)converts the whole sheet to a 2D array. The four parameters mean: null value placeholder, calculate formulas, format numbers/dates, return column letters as array keys.
Step 3: A Production-Ready Reader with Headers
The basic version returns numbered rows and letter-keyed columns (A, B, C). For real use, you want associative arrays keyed by your header row.
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
function readExcelToArray(string $filePath, int $sheetIndex = 0): array {
if (!file_exists($filePath) || !is_readable($filePath)) {
throw new RuntimeException("File not found or unreadable: {$filePath}");
}
try {
$spreadsheet = IOFactory::load($filePath);
} catch (ReaderException $e) {
throw new RuntimeException("Could not read Excel file: " . $e->getMessage());
}
$worksheet = $spreadsheet->getSheet($sheetIndex);
$rows = $worksheet->toArray(null, true, true, false);
if (empty($rows)) {
return [];
}
// First row = headers
$headers = array_map('trim', array_shift($rows));
$data = [];
foreach ($rows as $row) {
// Skip fully empty rows
if (count(array_filter($row, fn($v) => $v !== null && $v !== '')) === 0) {
continue;
}
$data[] = array_combine($headers, $row);
}
return $data;
}
// Usage
$users = readExcelToArray('users.xlsx');
foreach ($users as $user) {
echo $user['Name'] . ' - ' . $user['Email'] . PHP_EOL;
}
Why this version is better:
- Returns associative arrays so you can access fields by header name (
$user['Email']) instead of$user['B']. - Catches reader exceptions so corrupted files don’t crash your script.
- Skips empty rows that Excel often leaves at the bottom of sheets.
- Trims headers so accidental whitespace doesn’t break your keys.
- Accepts a sheet index so you can read any worksheet, not just the first.
Step 4: Reading Specific Cells, Rows, and Ranges
Sometimes you don’t need the whole sheet — just a few cells or a specific range:
$worksheet = $spreadsheet->getActiveSheet();
// Read a single cell by coordinate
$name = $worksheet->getCell('B2')->getValue();
// Read a single cell by row/column index (column 2 = B, row 2)
$email = $worksheet->getCell([2, 2])->getValue();
// Read calculated value of a formula cell
$total = $worksheet->getCell('D10')->getCalculatedValue();
// Read a specific range as an array
$rangeData = $worksheet->rangeToArray('A2:D20', null, true, true, false);
// Get highest used row and column
$lastRow = $worksheet->getHighestRow();
$lastCol = $worksheet->getHighestColumn();
echo "Sheet has {$lastRow} rows and goes up to column {$lastCol}\n";
The difference between getValue() and getCalculatedValue() matters: getValue() returns the raw formula like =SUM(A1:A10), while getCalculatedValue() returns the result (e.g. 42).
Step 5: Working With Multiple Worksheets
Excel files often have multiple sheets (tabs at the bottom). Here’s how to handle them:
$spreadsheet = IOFactory::load('multi-sheet.xlsx');
// Get list of all sheet names
$sheetNames = $spreadsheet->getSheetNames();
print_r($sheetNames);
// e.g. ['Customers', 'Orders', 'Products']
// Loop through all sheets
foreach ($spreadsheet->getAllSheets() as $sheet) {
echo "Sheet: " . $sheet->getTitle() . "\n";
echo "Rows: " . $sheet->getHighestRow() . "\n\n";
}
// Get a sheet by name
$ordersSheet = $spreadsheet->getSheetByName('Orders');
$orders = $ordersSheet->toArray();
// Get a sheet by index
$firstSheet = $spreadsheet->getSheet(0);
Step 6: Reading Large Excel Files Without Running Out of Memory
PhpSpreadsheet loads the entire file into memory by default. For a 50MB+ XLSX with hundreds of thousands of rows, this will exhaust your memory limit. Two solutions:
Option A: Read-only mode + chunk filter
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
class ChunkReadFilter implements IReadFilter {
private int $startRow;
private int $endRow;
public function setRows(int $startRow, int $chunkSize): void {
$this->startRow = $startRow;
$this->endRow = $startRow + $chunkSize;
}
public function readCell($column, $row, $worksheetName = ''): bool {
return $row >= $this->startRow && $row < $this->endRow;
}
}
$filePath = 'huge.xlsx';
$chunkSize = 1000;
$reader = IOFactory::createReaderForFile($filePath);
$reader->setReadDataOnly(true); // Skip formatting — much faster
$chunkFilter = new ChunkReadFilter();
$reader->setReadFilter($chunkFilter);
$startRow = 2; // Skip header row
while (true) {
$chunkFilter->setRows($startRow, $chunkSize);
$spreadsheet = $reader->load($filePath);
$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
if (empty($sheetData)) break;
foreach ($sheetData as $row) {
// Process one row — insert into DB, send to API, etc.
}
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
$startRow += $chunkSize;
}
Option B: Use OpenSpout for true streaming
For truly massive files (millions of rows), use OpenSpout — it streams rows one at a time without ever loading the file into memory:
composer require openspout/openspout
use OpenSpout\Reader\XLSX\Reader;
$reader = new Reader();
$reader->open('massive.xlsx');
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
$cells = $row->toArray();
// Process row — uses constant memory regardless of file size
}
}
$reader->close();
OpenSpout uses ~5MB of memory whether your file has 1,000 rows or 10 million.
Step 7: Handling Excel Quirks (Dates, Formulas, Merged Cells)
Excel dates are numbers, not strings
A date like 2025-11-04 is stored as the number 45965 (days since 1900-01-01). Convert it back:
use PhpOffice\PhpSpreadsheet\Shared\Date;
$rawValue = $worksheet->getCell('A2')->getValue(); // 45965
$dateTime = Date::excelToDateTimeObject($rawValue);
echo $dateTime->format('Y-m-d'); // 2025-11-04
Or just use the formatted parameter when calling toArray():
$rows = $worksheet->toArray(null, true, true, true); // 4th param formats dates as text
Reading merged cells
PhpSpreadsheet returns the value only in the top-left cell of a merged range — other cells return null. Check for merges:
foreach ($worksheet->getMergeCells() as $mergeRange) {
echo "Merged range: {$mergeRange}\n"; // e.g. "A1:C1"
}
Skipping formula calculation (faster reading)
Calculating formulas is slow. If you only need the cached values that Excel saved:
$reader = IOFactory::createReaderForFile($filePath);
$reader->setReadDataOnly(true); // No formatting, no calculation — fastest
$spreadsheet = $reader->load($filePath);
Common Errors and Fixes
“Class ‘PhpOffice\PhpSpreadsheet\IOFactory’ not found”
You forgot the autoloader. Add require 'vendor/autoload.php'; at the top.
“Allowed memory size exhausted”
The file is too big for default settings. Increase memory or switch to OpenSpout / chunk filter:
ini_set('memory_limit', '512M');
“Could not open file for reading! File does not exist.”
Check the file path is absolute or relative to the script’s working directory. Use realpath($filePath) to debug.
Encoding issues with special characters
PhpSpreadsheet handles UTF-8 natively. If you see garbled text, the issue is likely in how you’re outputting the data, not reading it. Make sure your output headers say UTF-8:
header('Content-Type: text/html; charset=utf-8');
Old .xls files (Excel 97-2003) fail to load
You need the optional dependency:
composer require phpoffice/phpspreadsheet
PhpSpreadsheet supports XLS natively, but very old or corrupted files sometimes need to be re-saved as XLSX in Excel first.
Quick Reference: PhpSpreadsheet Reader Methods
| Method | What it does |
|---|---|
IOFactory::load($path) |
Loads any supported file format (auto-detect) |
IOFactory::createReaderForFile($path) |
Creates a reader without loading — for filters/options |
$reader->setReadDataOnly(true) |
Skip formatting (faster, less memory) |
$reader->setReadFilter($filter) |
Apply a chunk/column filter |
$spreadsheet->getActiveSheet() |
Get currently active worksheet |
$spreadsheet->getSheet($index) |
Get worksheet by zero-based index |
$spreadsheet->getSheetByName('Name') |
Get worksheet by tab name |
$worksheet->toArray() |
Convert sheet to 2D PHP array |
$worksheet->rangeToArray('A1:D10') |
Convert specific range to array |
$worksheet->getCell('B2')->getValue() |
Read one cell’s raw value |
$worksheet->getCell('B2')->getCalculatedValue() |
Read one cell’s calculated value |
Wrapping Up
For 99% of Excel reading tasks in PHP, PhpSpreadsheet is the right answer — install with Composer, load the file, convert to array, done. The production-ready function above handles headers, exceptions, and empty rows so you can drop it into any project.
For huge files where memory is a concern, switch to OpenSpout for true row-by-row streaming, or use PhpSpreadsheet’s chunk filter pattern.
The biggest gotchas to remember: Excel dates are numeric, formulas have raw vs calculated values, and setReadDataOnly(true) is your friend for performance. Get those right and you’ll handle anything Excel throws at you.
If you’re building an upload feature where users submit Excel files, always validate the data after reading — never trust user input, and use prepared statements when saving to a database.
Be the first to comment