spatie/simple-excel
Lightweight reader/writer for simple CSV and XLSX files in PHP/Laravel. Uses generators and LazyCollection for low memory usage on large files. Quickly stream rows for processing or export data without loading entire spreadsheets into memory.
Installation:
composer require spatie/simple-excel
First Use Case - Reading a CSV:
use Spatie\SimpleExcel\SimpleExcelReader;
SimpleExcelReader::create('path/to/file.csv')
->getRows()
->each(function (array $row) {
// Process each row
});
First Use Case - Writing a CSV:
use Spatie\SimpleExcel\SimpleExcelWriter;
SimpleExcelWriter::create('path/to/output.csv')
->addRow(['name' => 'John', 'email' => 'john@example.com'])
->close();
Key Documentation:
Basic Row Processing:
SimpleExcelReader::create('users.csv')
->getRows()
->each(fn(array $row) => User::create($row));
Chunk Processing for Large Files:
SimpleExcelReader::create('large_file.xlsx')
->chunk(100, fn(array $rows) => User::insert($rows));
Transforming Data:
SimpleExcelReader::create('data.csv')
->getRows()
->map(fn(array $row) => [
'full_name' => $row['first_name'] . ' ' . $row['last_name'],
'email' => strtolower($row['email']),
]);
Multi-Sheet Handling:
$sheetNames = SimpleExcelReader::create('report.xlsx')->getSheetNames();
foreach ($sheetNames as $sheet) {
SimpleExcelReader::create('report.xlsx')
->fromSheetName($sheet)
->getRows()
->each(fn(array $row) => /* ... */);
}
Batch Writing:
$writer = SimpleExcelWriter::create('output.xlsx');
foreach ($users as $user) {
$writer->addRow($user->toArray());
}
$writer->close();
Streaming to Browser:
SimpleExcelWriter::streamDownload('export.xlsx')
->addRows($users->map(fn($user) => $user->toArray())->toArray())
->toBrowser();
Dynamic Headers:
$writer = SimpleExcelWriter::create('dynamic.xlsx')
->addHeader(['name', 'email', 'created_at'])
->addRows($users->map(fn($user) => [
$user->name,
$user->email,
$user->created_at->format('Y-m-d'),
])->toArray());
$writer->close();
Custom Formatting:
$writer = SimpleExcelWriter::create('formatted.xlsx')
->formatHeadersUsing(fn($header) => ucfirst($header))
->addRows($data);
$writer->close();
Laravel Artisan Commands:
use Spatie\SimpleExcel\SimpleExcelReader;
class ImportUsersCommand extends Command
{
protected $signature = 'import:users {file}';
public function handle()
{
SimpleExcelReader::create($this->argument('file'))
->getRows()
->each(fn(array $row) => User::create($row));
}
}
API Endpoints:
Route::post('/import', function (Request $request) {
$request->validate(['file' => 'required|file']);
$file = $request->file('file')->store('temp');
SimpleExcelReader::create(storage_path("app/{$file}"))
->getRows()
->each(fn(array $row) => User::create($row));
return response()->json(['status' => 'imported']);
});
Queue Jobs:
class ImportUsersJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle()
{
SimpleExcelReader::create(storage_path('app/import.csv'))
->chunk(50, fn(array $rows) => User::insert($rows));
}
}
Service Providers:
class ExcelServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(ExcelReader::class, fn() => new SimpleExcelReader());
}
}
Memory Leaks with Large Files:
close() on SimpleExcelWriter can cause memory leaks.$writer->close() or use streamDownload() for large files.LazyCollection Pitfalls:
filter() or map() on LazyCollection can lead to unexpected behavior if not used with getRows().getRows() before chaining methods:
SimpleExcelReader::create('file.csv')
->getRows()
->filter(fn(array $row) => $row['active'])
->each(/* ... */);
Excel Formula Handling:
keepFormulas() to preserve them.SimpleExcelReader::create('formulas.xlsx')
->keepFormulas()
->getRows();
DateTime Handling:
DateTimeImmutable by default. Use preserveDateTimeFormatting() to keep original formatting.SimpleExcelReader::create('dates.xlsx')
->preserveDateTimeFormatting()
->getRows();
Empty Rows:
preserveEmptyRows() to include them.SimpleExcelReader::create('file.xlsx')
->preserveEmptyRows()
->getRows();
Sheet Indexing:
fromSheet() uses 0-based indexing. Off-by-one errors can occur.$sheetNames = SimpleExcelReader::create('file.xlsx')->getSheetNames();
$sheetIndex = array_search('Sheet1', $sheetNames);
Inspect Headers:
$headers = SimpleExcelReader::create('file.csv')->getHeaders();
dd($headers);
Log Rows:
SimpleExcelReader::create('file.csv')
->getRows()
->take(5)
->each(fn(array $row) => \Log::info($row));
Check File Paths:
$path = storage_path('app/imports/file.csv');
Validate Data:
SimpleExcelReader::create('file.csv')
->getRows()
->each(fn(array $row) => validator($row, ['email' => 'required|email'])->validate());
Custom OpenSpout Configuration:
OpenSpout configurations via withOpenSpoutConfig():
SimpleExcelReader::create('file.xlsx')
->withOpenSpoutConfig([
'open_spout_config' => [
'readerType' => 'XLSX',
'chunkSize' => 1000,
],
]);
CSV Delimiters:
SimpleExcelReader::create('custom.csv')
->setDelimiter(';')
->getRows();
Excel Encoding:
SimpleExcelReader::create('file.xlsx')
->setEncoding('UTF-8');
Custom Row Processing:
map() or transform() on LazyCollection:
SimpleExcelReader::create('file.csv')
->getRows()
->map(fn(array $row) => [
'name' => strtoupper($row['name']),
'email' => strtolower($row['email']),
]);
Pre-Process Headers:
formatHeadersUsing() for custom header transformations:
SimpleExcelReader::create('file.csv')
->formatHeadersUsing(fn($header) => str_replace(' ', '_', $header))
->getRows();
**Post-Process Rows
How can I help you explore Laravel packages today?