avadim/fast-excel-writer
Fast, memory-efficient PHP library for writing Excel XLSX files. Streams rows to keep memory low and supports multiple sheets, styling, formulas, merged cells, images, and large datasets. A lightweight alternative to PhpSpreadsheet for export-heavy Laravel/PHP apps.
Architecture fit. avadim/fast-excel-writer is a framework-agnostic, write-only XLSX generator that assembles OOXML directly rather than through PhpSpreadsheet. For a Laravel application the fit is narrow but high-value: it belongs on the export path only, behind a reporting/export service, not as a general spreadsheet abstraction. Its value proposition — low memory, streaming row-by-row writes, chart and style support — maps directly onto the most common Laravel pain point (large exports under maatwebsite/excel exhausting memory or timing out). It has no reader, no formula calculation engine, and no template rendering, so it cannot replace the incumbent for imports, round-tripping, or user-facing "download and re-upload" workflows.
Integration feasibility. Composer-installable, MIT, no service provider, no facade, no config publish. Integration is a plain new Excel() in a job or service class plus our own DI binding — under a day of work for a first report. The real cost is not installation but encapsulation: we must own the adapter, the queued export lifecycle, storage handoff, and filename/MIME conventions. Confirm the runtime requirements (PHP floor, ext-zip/ext-mbstring) against our CI matrix before committing; the package's requirement set differs from PhpSpreadsheet's and may be stricter on PHP version than our fleet.
Technical risk.
=, +, -, @ can be interpreted as formulas — the XLSX equivalent of CSV injection. The library will not sanitize this for us; it must be handled in our adapter.Key questions. Which of our reports are genuinely memory- or time-constrained enough to justify a second writer alongside the incumbent? Do we need charts, or only styling and formulas? Can we accept "write-only, no round-trip" in our product contract? Who owns the fork if the maintainer goes dormant? What is our PHP runtime floor versus the package's?
Stack fit. Laravel-agnostic by design, which is both the appeal and the gap: nothing here knows about queues, filesystems, notifications, or Storage. That is fine — those are our concerns and we already have them. The package slots in as the serialization layer inside an existing export job, with Storage::disk('s3')->putFileAs() handling delivery and a signed URL returned to the UI. It coexists happily with maatwebsite/excel for imports and small exports; there is no need for a big-bang replacement.
Migration path. Do not migrate the portfolio. Sequence: (1) timeboxed spike on the single largest, most memory-painful report; (2) define an internal SpreadsheetWriter interface (writeHeader, writeRows, addChart, save) with two adapters — incumbent and this package; (3) route new and high-volume reports through the new adapter; (4) leave complex/legacy workbooks on the incumbent; (5) revisit consolidation only if the adapter proves stable across two quarters.
Compatibility. Pin an exact version with a caret-constrained lockfile entry and a composer.json comment recording the rationale. Because there is no Laravel coupling, framework upgrades will not break it — an underrated durability advantage over packages that track Laravel majors. Watch PHP minor-version deprecations (dynamic properties, implicit nullable) as the codebase ages; expect to submit or carry patches.
Sequencing. Contract test first: generate a fixture workbook in CI, unzip it, and validate the XML plus open it with a throwaway reader (dev-only dependency) to assert values, styles, and chart parts. Then adapter, then the pilot report behind a feature flag, then broaden. Budget one sprint for pilot plus hardening, not one day for "install and go."
Maintenance. Effectively unmaintained-by-default: MIT, one maintainer, no vendor relationship, no SLA. Our mitigation is structural — pinned version, adapter boundary, a documented fork-and-patch procedure, and a two-file surface area (adapter + one job) so a swap costs days, not months. Track the upstream repo on a quarterly review; if commits stop for two release cycles, freeze and plan substitution.
Support. GitHub issues only, in the maintainer's timezone and priority order. Escalation path is us. Document that explicitly so on-call does not expect upstream help during an incident.
Scaling. This is the package's strongest operational argument. Row-at-a-time streaming keeps memory flat as report size grows; the binding constraint shifts to CPU, disk I/O, and Excel's own row limit (1,048,576), not PHP memory. Consequences for architecture: run generation in queued jobs with a dedicated queue, write to a local temp path then push to object storage, and enforce a row cap with a clear product message. Watch chart count and shared-string growth for pathological reports; instrument generation time and peak memory per report type so regressions are visible.
Failure modes.
try/finally with temp-file cleanup.cursor() usage in code review.Ramp-up. Low for the API (fluent, example-driven, learnable in a day); moderate for the domain (Excel number formats, style hierarchies, chart XML are intrinsically fiddly and the library is a thin surface over them). Documentation is README-and-examples depth, so expect tribal knowledge to concentrate in one or two engineers. Mitigate with an internal cookbook of our three or four recurring report patterns, the adapter contract, and the CI fixture test — that turns a single-maintainer dependency into an institutional capability.
How can I help you explore Laravel packages today?