Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Xlswriter Laravel Package

viest/xlswriter

High-performance PHP extension for reading and writing Excel XLSX files with low memory usage. Built in C on libxlsxwriter, it supports formulas, charts, images, formatting, conditional formatting and data validation—fast, reliable and ideal for large exports.

View on GitHub
Deep Wiki
Context7

Getting Started

xlswriter is a PHP C extension (not a Composer package) — there is nothing to require in composer.json. Installation is the first real hurdle:

# Option A: PECL
pecl install xlswriter

# Option B: compile from source (needs php-dev + zlib)
git clone https://github.com/viest/php-ext-xlswriter.git
cd php-ext-xlswriter && phpize && ./configure && make && make install

Then enable it and confirm it loaded:

; php.ini
extension=xlswriter.so   ; or xlswriter.dll on Windows
php -m | grep xlswriter   # must appear, otherwise every call below is a fatal error

Look first at: the Vtiful\Kernel\Excel class — it is the only entry point. Everything is fluent: fileName() → header() → addSheet()/getHandle() → write*() → output()/close(). The repo's tests/ directory is the de-facto API reference.

First use case — replace PhpSpreadsheet for write-only XLSX export:

use Vtiful\Kernel\Excel;

$config = ['path' => storage_path('app/exports')]; // a DIRECTORY, not a file path
$filePath = (new Excel(['path' => storage_path('app/exports')]))
    ->fileName('users.xlsx')
    ->header(['ID', 'Name', 'Created'])
    ->data([[1, 'Ada', '2026-01-01'], [2, 'Grace', '2026-01-02']])
    ->output();

output() returns the absolute path of the written file. That is the thing you Storage::download() or response()->download(), not the fileName() string.

Implementation Patterns

Streaming large exports. The reason to pick this extension over PhpSpreadsheet is memory. Use openFile() + row-by-row writeRow() inside a chunked query loop rather than building a giant array:

$excel = new Excel(['path' => $dir]);
$handle = $excel->fileName('big.xlsx')->header(['id', 'name'])->getHandle();

User::query()->chunkById(5000, function ($rows) use ($handle, $excel) {
    foreach ($rows as $i => $u) {
        $excel->writeRow($i, 0, [$u->id, $u->name]); // or insertText()
    }
});
$excel->output();

Note the $i offset: writeRow takes an explicit row index and will silently overwrite if you don't advance it yourself. Track a counter outside the chunk callback.

Column formats via addFormat(). Reuse the resource returned by addFormat() across cells — creating one per cell leaks handles and slows the write:

$bold  = $excel->addFormat()->bold()->fontColor('#FFFFFF')->background('#4F81BD')->wrap();
$money = $excel->addFormat()->number('#,##0.00');
$date  = $excel->addFormat()->number('yyyy-mm-dd');

$excel->insertText(0, 0, 'Total', $bold);
$excel->insertText(1, 2, 1234.5, $money);
$excel->insertDate(1, 3, time(), $date); // insertDate expects a unix timestamp

insertDate()/insertUrl()/insertChart() exist because a plain insertText cannot express those types — type is decided by the method, not by the PHP value.

Laravel controller download. Write to a temp dir, then hand back a proper response and clean up in terminate():

public function export(): BinaryFileResponse
{
    $path = (new Excel(['path' => storage_path('app/tmp')]))
        ->fileName('report_'.now()->timestamp.'.xlsx')
        ->header(['a', 'b'])
        ->data($this->rows())
        ->output();

    return response()->download($path)->deleteFileAfterSend(true);
}

deleteFileAfterSend(true) is essential — this extension never cleans up after itself.

Global default format. If most cells share a style, set it once on the workbook instead of passing a format to every call:

$excel->fileName('x.xlsx')->setGlobalFormat($excel->addFormat()->font('Arial')->fontSize(10));

Freeze panes / autofilter / merges go on the sheet handle, after addSheet() but before writing rows:

$excel = new Excel(['path' => $dir]);
$excel->fileName('x.xlsx')
      ->addSheet('Q1')
      ->freezePanes(1, 0)          // freeze header row
      ->autoFilter('A1:D1')
      ->mergeCells(0, 0, 0, 3, 'Q1 Summary') // (row1,col1,row2,col2,text)
      ->setColumn('A:A', 20);

Multi-sheet workbooks. Call addSheet() for each sheet; each returns a fresh handle, and writes go to the most recently added sheet. If you need to interleave, keep getHandle() output per sheet and be aware the extension is stateful and single-cursor.

Gotchas and Tips

  • path is a directory. Passing storage_path('app/x.xlsx') as path produces a file literally named x.xlsx inside a non-existent directory, or fails silently. Split directory and filename.
  • Silent failures are the norm. fileName() and write*() rarely throw; the library returns the string path or a resource. Always file_exists() the result of output() before you serve it, and log if not.
  • Reading is not supported. This package is write-only by design. For import/parsing use openspout/openspout (streaming) or PhpSpreadsheet. Don't try to force xlswriter into a read-modify-write workflow.
  • Formulas are not evaluated. insertFormula() writes the formula string; Excel computes on open. If your pipeline later reads the value back without Excel opening it, pass the cached value as the second argument or you get 0/blank.
  • Dates are numbers. insertDate() takes a timestamp and requires a format object with a date number format. Write '2026-01-01' as a string and it lands as text, un-sortable.
  • Row/column indices are zero-based, including header rows. Header row is row 0. Off-by-one is the most common bug you'll hit.
  • getHandle() vs fluent return. Mixing ->getHandle() storage with fluent chaining breaks the builder — pick one style per method chain. The handle is what writeRow()/write() need.
  • Memory mode vs temp file. Excel(['path' => ..., 'memory' => true]) keeps the workbook in RAM (fast, but O(file size)). Leave it off for large exports; the default spills to disk in the configured path.
  • openFile() before output() if you wrote via the handle path — calling output() on a closed/abandoned builder writes a truncated file with no error. Prefer close() for explicit finalization in long-running workers.
  • Concurrency: a given path directory is where temp files land; in a queue worker, use a per-job unique directory (storage_path('app/tmp/'.$jobId)) and clean it up, or parallel jobs will collide on temp filenames.
  • CI/Docker: the extension isn't in php:8.x-cli images. Add pecl install xlswriter in your Dockerfile and pin the version — the "last release" date being ahead of normal implies very active/rolling releases; a version bump can change format behavior.
  • Extension points: styling constants live as Vtiful\Kernel\Format options; conditional formatting and data validation are exposed as methods on the sheet handle. If you need a feature that isn't wired up, file an issue — you cannot polyfill inside PHP because the logic is in C.
  • Benchmark check: if you're only producing small (<5k row) files, PhpSpreadsheet is dependency-free and far easier to deploy. Reach for xlswriter when row counts and memory are the bottleneck.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment