# rap2hpoutre/fast-excel: Laravel xlsx and csv import/export built on OpenSpout

> A Laravel package that wraps OpenSpout for streaming Excel and CSV import and export, with column selection, row ranges and multi-sheet support. The trade-off is that it is a thin layer over Spout, so its limits are Spout's limits.

**rap2hpoutre/fast-excel** — 🦉 Fast Excel import/export for Laravel

- Repository: https://github.com/rap2hpoutre/fast-excel
- Stars: 2,383 · Forks: 271
- Language: PHP
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/rap2hpoutre-fast-excel

## What fast-excel is for in a Laravel codebase

The package targets one job: moving tabular data between a Laravel application and a spreadsheet file without loading the whole sheet into memory. The README states the project is fast "thanks to Spout", and the repository's composer.json and src/ directory show it as a wrapper around the Spout reader and writer rather than a spreadsheet engine of its own. That framing matters. Anything the underlying library cannot parse, fast-excel cannot parse either.

The API is built around two verbs. export() takes a Model, a Query or a Collection and writes a file, returning the absolute path to what it wrote. import() reads a file and returns a Collection. Both accept closures that let you reshape rows on the way out or map them into records on the way in, which is where most application logic ends up living. A typical import callback calls User::create() per line, so the package is often the front door to a database write loop rather than a data-analysis tool.

## How the export and import paths actually flow

On export, you hand the constructor an iterable. The README's examples pass User::all(), a query result, or a plain collection of arrays. The writer walks that iterable and emits rows. When you pass a closure as the second argument to export(), the closure receives each item and returns an associative array whose keys become the header row and whose values become the cells. That is also where you can compute values, for example uppercasing a last name before it is written.

On import, the reader parses the sheet and hands each row to your callback as an associative array keyed by the header names. Without a callback, import() returns the whole thing as a Collection. The row-shaping methods sit between the reader and your code: limitRows caps how many data rows are read, startRow begins reading at a given row, headerRow tells the reader where the header names really live, limitColumns truncates each row after a given column, and onlyColumns keeps a chosen set and drops the rest. The README notes that OpenSpout still parses the sheet even when limitColumns drops trailing cells, so truncation saves you from junk keys in the result, not from parsing work.

The multi-sheet path is separate. Exporting several sheets means building a SheetCollection from several iterables, optionally keyed by sheet name. Importing uses importSheets() for all sheets or sheet() for one, and the README states that sheet() accepts either a number or a name.

## Installing fast-excel and running a first export and import

Installation is a single Composer command inside a Laravel project. The README gives it directly, and there is no separate service provider step documented.

```bash
composer require rap2hpoutre/fast-excel
```

The README's quick start exports a model to xlsx. Note that export() returns the absolute path of the file it wrote, which is useful when you want to log it or hand it to another process.

```php
use Rap2hpoutre\FastExcel\FastExcel;
use App\User;

$users = User::all();
$path = (new FastExcel($users))->export('file.xlsx');
```

Reading a file back gives you a Collection, and the callback form maps each row into a record. The README shows the callback receiving an associative array keyed by the header names.

```php
$users = (new FastExcel)->import('file.xlsx', function ($line) {
    return User::create([
        'name' => $line['Name'],
        'email' => $line['Email']
    ]);
});
```

If you prefer the facade, the README instructs adding an alias under the aliases key in config/app.php. The facade has no constructor, so data is set with the data() method instead.

```php
'FastExcel' => Rap2hpoutre\FastExcel\Facades\FastExcel::class,
```

```php
FastExcel::data($list)->export('file.xlsx');
```

There is also a global helper. The README shows fastexcel() used both to import and to wrap a collection for export.

## Chunked imports and the headerRow versus startRow distinction

This is the part of the API worth reading twice. The README states that startRow, used on its own, treats the start row as the header row. headerRow is opt-in and exists so that headers can be read from their real position while data begins further down. The documented reason is chunked imports: a large file is imported one slice per job run, and each slice needs the correct header names even though it does not start at row one.

```php
$chunk = (new FastExcel)
    ->headerRow(1)
    ->startRow(2 + ($page * 1000)) // data begins on row 2
    ->limitRows(1000)
    ->import('file.xlsx');
```

If you omit headerRow and only use startRow, the first row of your slice becomes the header row, and your column names silently become data values. That failure mode is quiet: you get a Collection back with plausible-looking keys, and the mistake surfaces later as missing fields. Anyone building a queue-driven import should pin headerRow explicitly rather than rely on the default.

The same section documents limitColumns as the fix for a related real-world problem. When formatting has been applied to entire rows, the spreadsheet reports thousands of trailing cells that look like columns, and importing them yields empty column_9, column_10 and so on for every row. limitColumns drops those cells from the imported collection. onlyColumns is the stricter alternative when you know which columns you want. The README states that onlyColumns and limitColumns cannot both be active, and that setting one clears the other; passing null clears only that setter.

## Where fast-excel is the wrong tool

It is a Laravel package. The facade registration goes in config/app.php, the helper is a Laravel global helper, and the README's examples are written against Eloquent models. If you are not in Laravel, the framework assumptions in the documented usage are friction you would have to work around, and the README does not describe a framework-free setup.

Second, the package does not do formatting. Nothing in the README covers cell styles, number formats, merged cells, formulas, charts or conditional formatting. If your deliverable is a presentation-grade workbook, this is not the layer for it. It writes values.

Third, memory behaviour is inherited. The README attributes the speed to Spout and the repository topics include memory-efficiency, but no benchmark numbers appear in the README text provided here beyond a link to a benchmarks section. Treat the performance claim as a design intent, not a measured guarantee you can quote to a stakeholder.

Finally, the row and column selection methods are stateful setters on the same object. onlyColumns clearing limitColumns is documented, but it means a shared or reused FastExcel instance can carry configuration from a previous call. Instantiate per operation.

## fast-excel compared with PhpSpreadsheet

The obvious alternative in PHP is PhpSpreadsheet, which is a full spreadsheet library rather than a Laravel wrapper. The difference is in what each one is built to do. PhpSpreadsheet models the workbook: sheets, cells, styles, formulas, and a grid you can address by coordinate. fast-excel models the transfer: an iterable in, a file out, or a file in, a Collection out.

That distinction drives the choice. If you need to read a template, write into specific cells, keep formatting, or produce a workbook a human will edit further, PhpSpreadsheet is the right shape and fast-excel is not, because fast-excel has no documented API for any of that. If you need to dump ten thousand Eloquent rows to xlsx on a schedule, or ingest an uploaded csv with a semicolon delimiter and gbk encoding, fast-excel's surface is smaller and the code you write is shorter. The README's configureCsv example covers exactly that case: delimiter, enclosure and encoding passed in one call.

One caveat on the comparison: fast-excel is a wrapper, so its ceiling is set upstream. When you need a feature the wrapper does not expose, you are either reaching into Spout directly or switching libraries.

## Maintenance, licensing and the cost of upgrading

The repository is not archived, and the last push was on 2026-09-14. The most recent release listed is v5.17.0 on 2026-09-08, with v5.16.0 on 2026-09-01 and v5.15.0 on 2026-08-16. That cadence suggests a project that is still being changed, though the README does not state a support policy, a deprecation policy, or a minimum PHP or Laravel version, so you cannot infer an upgrade contract from the documentation alone. The CHANGELOG.md file exists at the repository root, which is where a breaking change between minor versions would be recorded.

Licensing is MIT, per the LICENSE file and the Packagist badge. MIT is permissive: it allows commercial use and modification with attribution and no warranty. That is a statement about the licence text, not legal advice; if your organisation has a policy on bundled dependencies, the LICENSE file is what your reviewers will read.

The practical upgrade cost is low but not zero. Version 5 is the current line, and the API shown in the README is method chaining on a single class, so a major bump is the event that would touch your call sites. Because the package delegates parsing and writing to Spout, some upgrades will be driven by the upstream library rather than by this repository. Keep the CHANGELOG.md open during any bump.

## Conclusion

Adopt fast-excel when you are inside a Laravel application and your main need is to stream models, queries or collections into xlsx or csv, or to read a spreadsheet back in slices. Do not adopt it as a general-purpose spreadsheet library outside Laravel: the facade and the fastexcel() helper assume the framework, and the README does not document a standalone integration path. Before committing, check two things in the source of the version you install: how headerRow and startRow interact in your chunking loop, and whether onlyColumns or limitColumns is the one active on that instance, since setting one clears the other. Those two behaviours drive the correctness of chunked imports.

## FAQ

### What is rap2hpoutre/fast-excel?

It is a Laravel package for importing and exporting xlsx, csv and ods files, built on top of Spout. You pass it a model, query or collection to export, or a file path to import, and it returns either a written path or a Collection.

### How do I use fast-excel to export a Laravel model to xlsx?

Pass the model result to the constructor and call export() with a filename. The README example loads User::all() and calls (new FastExcel($users))->export('file.xlsx'), which returns the absolute path of the written file.

### How do I add rows to an existing file with fast-excel?

The README does not document appending to an existing workbook. Its export examples construct a new file from a model, query or collection, and the documented methods cover reading ranges, not writing into a file that already exists.

## Sources

- [Issues](https://github.com/rap2hpoutre/fast-excel/issues)
- [License: MIT](https://github.com/rap2hpoutre/fast-excel/blob/master/LICENSE)
- [rap2hpoutre/fast-excel on GitHub](https://github.com/rap2hpoutre/fast-excel)
- [README](https://github.com/rap2hpoutre/fast-excel/blob/master/README.md)
- [Releases](https://github.com/rap2hpoutre/fast-excel/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rap2hpoutre-fast-excel
