NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #783 most downloaded on Packagist
Fast Excel import/export for Laravel
Last release 29 days ago
08 Sep 2026
Ships unpredictably
gaps range from 8 days to 13 months
Rarely documented
notes for 12 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
9 years old
73 releases · first in 2018
startRow() alone used to double as the header row too, so reading a file starting anywhere but row 1 lost the real headers — the first data row got us
startRow() alone used to double as the header row too, so reading a file starting anywhere but row 1 lost the real headers — the first data row got used as the header names instead. headerRow() decouples the two.
$rows = (new FastExcel())->headerRow(1)->startRow(4)->import('file.xlsx');This is also what makes chunked imports work cleanly: every chunk can read the same header row while sliding its own startRow()/limitRows() window over the data. startRow() on its own keeps its historical meaning, so nothing breaks for existing callers.
Fixes #349, reported by @MaarekVarres.
Cell values starting with =, +, - or @ are treated as formulas by Excel and other spreadsheet software, which can silently execute unintended content when a file is opened. escapeFormulas() writes those values as plain text instead.
(new FastExcel($rows))->escapeFormulas()->export('file.xlsx');Fixes #356, reported by @LauKL1991. Thanks also to @ariprw and @gmutinel, whose '-prefix workarounds on the issue shaped the fix.
Thanks to @dannyyol for reviewing both of these.
Full changelog: v5.16.0...v5.17.0
One column per quarter.
limitColumns() and onlyColumns()
Imports can now be trimmed by column, not just by row.
limitColumns() truncates each row after a given column, taking either a column reference or a count:
$collection = (new FastExcel)->limitColumns('H')->import('file.xlsx');
$collection = (new FastExcel)->limitColumns(8)->import('file.xlsx');This is aimed at files where formatting has been applied to entire rows. The spreadsheet then reports thousands of trailing cells that look like real columns, and every imported row comes back padded with empty column_9, column_10… entries.
onlyColumns() keeps an explicit allowlist and drops everything else, including empty columns in the middle. Letters and 1-based indexes can be mixed, and the order you pass is the order you get:
$collection = (new FastExcel)->onlyColumns(['A', 'B', 'H'])->import('file.xlsx');
$collection = (new FastExcel)->onlyColumns([1, 2, 8])->import('file.xlsx');The two are mutually exclusive — setting one clears the other, while passing null clears only that setter. Both work with import and importLazy.
Thanks to @dannyyol, who opened #419 with the implementation and 346 lines of tests, then worked through a round of requested changes on onlyColumns() input validation without complaint. The request came from @chan15 in #370, asking for "something like an end column" — an accurate description of the problem that turned out to need two methods rather than one.
A row value may now be an OpenSpout\Common\Entity\Cell, for full control over a single cell's type or style. The pre-built cell is written through as-is while the other scalar values in the row keep their normal handling and per-column styles:
use OpenSpout\Common\Entity\Cell;
use OpenSpout\Common\Entity\Style\Style;
$users = collect([
['name' => 'John', 'note' => Cell::fromValue('paid', (new Style())->setFontBold())],
['name' => 'Jane', 'note' => 'pending'],
]);
(new FastExcel($users))->export('users.xlsx');Previously a Cell value was silently dropped by transformRow() and would have broken Row::fromValues(), which expects scalars.
Thanks to @kusab85, who proposed this in #306 and stayed in the thread through the discussion that shaped the final version; the merged commit carries their co-authorship. Thanks also to @duypdx for adding weight to the request.
Explicit column widths have worked all along through OpenSpout's writer options and configureOptionsUsing() — they were simply never written down, so #213 sat since 2021 with the answer "not possible", which was only half true.
(new FastExcel($list))
->configureOptionsUsing(function ($options) {
$options->setColumnWidth(40, 1); // first column
$options->setColumnWidth(15, 2, 3); // second and third
$options->setColumnWidthForRange(20, 1, 4); // or a contiguous span
})
->export('file.xlsx');The new section covers 1-based column numbers, streaming exports (widths are written when the file is finalized, not per row), and the csv caveat: OpenSpout\Writer\CSV\Options does not define setColumnWidth() at all, so calling it on a csv export raises Error: Call to undefined method rather than being quietly ignored.
Automatic sizing to fit content is still unavailable, so #213 stays open for that.
Thanks to @hemasharshar for raising it, and to everyone who kept the thread alive over four years with the real-world need behind it: @yybawang, @allanvb, @tanvir-retailai, @humbertleonardo and @tajweb. Thanks to @dannyyol for reviewing the docs PR, and to @rap2hpoutre for the earlier answers on the issue.
Full changelog: v5.15.0...v5.16.0
XLSX exports can now open as proper right-to-left sheets. Column order, scroll direction and the grid all run right to left, which is what Arabic busi
XLSX exports can now open as proper right-to-left sheets. Column order, scroll direction and the grid all run right to left, which is what Arabic business documents expect.
(new FastExcel($report))->rightToLeft()->export('report.xlsx');Multi-sheet exports are covered too: every sheet in a SheetCollection gets the setting, not just the first. Pass rightToLeft(false) to turn it back off. CSV and ODS ignore it, since neither format has an equivalent.
Thanks to @moemadeldin for this one. He opened #420 explaining why configureWriterUsing() couldn't reach the setting (the sheet doesn't exist yet when that hook runs), then sent #421 with the fix and tests covering both the single-sheet and multi-sheet paths. The issue write-up did half the work.
importSheets() can now pass the sheet name to your callback, so the same field names can be treated differently depending on which sheet they came from.
$sheets = (new FastExcel)
->withSheetContext()
->importSheets('file.xlsx', function ($sheetName, $row) {
return $row + ['_sheet' => $sheetName];
});Requested in #369 by @StarkOne, whose earlier patch shaped the final version.
Full changelog: v5.14.0...v5.15.0
feat: select import sheet by name ( #273 ) by @elminson in #409 — originally proposed by @ElRochito in #285
Full Changelog: v5.13.0...v5.14.0
feat: limit imported rows with limitRows() ( #248 ) by @elminson in #413
Full Changelog: v5.12.0...v5.13.0
ci: benchmark PRs against base (time + peak memory) by @elminson in #407
Full Changelog: v5.11.0...v5.12.0
test: cover multi-sheet export with a Traversable per sheet ( #252 ) by @elminson in #402
ci: exclude tests/ from Codacy analysis by @elminson in #392
Full Changelog: v5.9.0...v5.10.0
docs: show that export() returns the written file path by @elminson in #390
Full Changelog: v5.8.0...v5.9.0
Fix php84 deprecations by @dannyyol in #383
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Move from box/spout to openspout/openspout v3 | https://github.com/openspout/openspout
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
(breaking change) drop support for PHP 7.1
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →