PHPackages                             wilsonglasser/spout - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PDF &amp; Document Generation](/categories/documents)
4. /
5. wilsonglasser/spout

ActiveLibrary[PDF &amp; Document Generation](/categories/documents)

wilsonglasser/spout
===================

PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way

v3.0.21(6mo ago)562.6k↓54.7%5Apache-2.0PHPPHP &gt;=5.6.0

Since Mar 27Pushed 1w ago1 watchersCompare

[ Source](https://github.com/wilsonglasser/spout)[ Packagist](https://packagist.org/packages/wilsonglasser/spout)[ Docs](https://www.github.com/box/spout)[ RSS](/packages/wilsonglasser-spout/feed)WikiDiscussions master Synced 2w ago

READMEChangelog (10)Dependencies (2)Versions (25)Used By (0)

SpoutX
======

[](#spoutx)

**SpoutX** is a fast, low-memory PHP library to **read and write XLSX** spreadsheet files.

It is a modernized, XLSX-only fork of the (discontinued) [Spout](https://github.com/box/spout) library, rebuilt for **PHP 8.4+**. Unlike Spout and its successor OpenSpout, SpoutX keeps a **mutable API** on purpose — the reason this fork exists is to let you build up cells, rows and styles imperatively — while adding first-class support for merged cells, comments, formulas with precomputed values, column dimensions/auto-size, auto filters, row heights and custom number formats.

- **XLSX only** — CSV and ODS support has been removed to keep the library small and focused.
- **PHP 8.4+** — `declare(strict_types=1)` everywhere, backed enums, typed properties, constructor promotion.
- **Mutable by design** — entities (`Cell`, `Row`, `Style`, …) are built and mutated in place; no `readonly`/`withX` ceremony.
- **Streaming &amp; scalable** — rows are written to disk as you go, so very large files stay within a small memory footprint.

---

Requirements
------------

[](#requirements)

- PHP **&gt;= 8.4**
- Extensions: `ext-dom`, `ext-mbstring`, `ext-xmlreader`, `ext-zip`

Installation
------------

[](#installation)

```
composer require wilsonglasser/spoutx
```

Root namespace is `SpoutX\` (no vendor prefix).

---

Quick start — writing
---------------------

[](#quick-start--writing)

```
use SpoutX\Common\Type;
use SpoutX\Writer\Common\Creator\WriterEntityFactory;

$writer = WriterEntityFactory::createWriter(Type::XLSX);
$writer->openToFile('/path/to/file.xlsx');   // or ->openToBrowser('file.xlsx')

// Add rows from plain arrays…
$writer->addRow(WriterEntityFactory::createRowFromArray(['Name', 'Age', 'Active']));
$writer->addRow(WriterEntityFactory::createRowFromArray(['Alice', 30, true]));

// …or add several at once
$writer->addRows([
    WriterEntityFactory::createRowFromArray(['Bob', 25, false]),
    WriterEntityFactory::createRowFromArray(['Carol', 41, true]),
]);

$writer->close();
```

Quick start — reading
---------------------

[](#quick-start--reading)

```
use SpoutX\Common\Type;
use SpoutX\Reader\Common\Creator\ReaderEntityFactory;

$reader = ReaderEntityFactory::createReader(Type::XLSX);
// shortcut that infers the type from the extension:
// $reader = ReaderEntityFactory::createReaderFromFile('/path/to/file.xlsx');

$reader->open('/path/to/file.xlsx');

foreach ($reader->getSheetIterator() as $sheet) {
    echo "Sheet: {$sheet->getName()}\n";
    foreach ($sheet->getRowIterator() as $row) {
        $values = $row->toArray();          // array of scalar cell values
        // or iterate cells: foreach ($row->getCells() as $cell) { $cell->getValue(); }
        print_r($values);
    }
}

$reader->close();
```

**Reader options** (call before `open()`):

```
$reader->setShouldFormatDates(true);         // return formatted date strings instead of DateTime
$reader->setShouldPreserveEmptyRows(true);   // keep empty rows in the iteration
$reader->setTempFolder('/custom/tmp');       // XLSX only
```

---

Cells and types
---------------

[](#cells-and-types)

A `Cell` wraps a value; its type is detected automatically and exposed as the `CellType` enum.

```
use SpoutX\Common\Entity\Cell;
use SpoutX\Common\Entity\CellType;

$cell = new Cell(42);
$cell->getType();        // CellType::Numeric
$cell->isNumeric();      // true
$cell->getValue();       // 42
```

`CellType` cases: `Numeric`, `String`, `Formula`, `Empty`, `Boolean`, `Date`, `Error`.

Supported values: `string`, `int`, `float`, `bool`, `\DateTime`/`\DateInterval`, `''`/`null` (empty), and formula strings starting with `=`.

---

Styling
-------

[](#styling)

Build styles with `StyleBuilder` and apply them per row (via the row/factory) or per cell.

```
use SpoutX\Writer\Common\Creator\WriterEntityFactory;
use SpoutX\Writer\Common\Creator\Style\StyleBuilder;
use SpoutX\Common\Entity\Style\Color;
use SpoutX\Common\Entity\Style\CellAlignment;
use SpoutX\Common\Entity\Style\CellVerticalAlignment;

$header = (new StyleBuilder())
    ->setFontBold()
    ->setFontSize(14)
    ->setFontName('Calibri')
    ->setFontColor(Color::WHITE)
    ->setBackgroundColor(Color::DARK_RED)
    ->setHorizontalAlign(CellAlignment::Center)
    ->setVerticalAlign(CellVerticalAlignment::Center)
    ->setShouldWrapText()
    ->build();

$writer->addRow(WriterEntityFactory::createRowFromArray(['Report'], $header));
```

`StyleBuilder` methods: `setFontBold()`, `setFontItalic()`, `setFontUnderline()`, `setFontStrikethrough()`, `setFontSize(int)`, `setFontName(string)`, `setFontColor(string)`, `setBackgroundColor(string)`, `setShouldWrapText(bool = true)`, `setShrinkToFit(bool = false)`, `setHorizontalAlign(CellAlignment)`, `setVerticalAlign(CellVerticalAlignment)`, `setBorder(Border)`, `setFormat(string)`, `setNumberFormat(NumberFormat)`, `setRowHeight(float)`.

**Alignment enums**

- `CellAlignment` (horizontal): `Left`, `Right`, `Center`, `General`
- `CellVerticalAlignment`: `Top`, `Center`, `Bottom`

**Colors**

Named constants (`Color::BLACK`, `WHITE`, `RED`, `DARK_RED`, `ORANGE`, `YELLOW`, `LIGHT_GREEN`, `GREEN`, `LIGHT_BLUE`, `BLUE`, `DARK_BLUE`, `PURPLE`) or build your own:

```
$rgb = Color::rgb(255, 192, 0); // "FFC000"
```

**Borders**

```
use SpoutX\Writer\Common\Creator\Style\BorderBuilder;
use SpoutX\Common\Entity\Style\Color;
use SpoutX\Common\Entity\Style\BorderWidth;
use SpoutX\Common\Entity\Style\BorderStyle;

$border = (new BorderBuilder())
    ->setBorderTop(Color::RED, BorderWidth::Thin, BorderStyle::Solid)
    ->setBorderBottom(Color::BLACK, BorderWidth::Medium, BorderStyle::Dashed)
    ->build();

$style = (new StyleBuilder())->setBorder($border)->build();
```

- `BorderWidth`: `Thin`, `Medium`, `Thick`
- `BorderStyle`: `None`, `Solid`, `Dashed`, `Dotted`, `Double`
- Sides: `setBorderTop()`, `setBorderRight()`, `setBorderBottom()`, `setBorderLeft()`

**Row height**

```
$style = (new StyleBuilder())->setRowHeight(50)->build();
$writer->addRow(WriterEntityFactory::createRowFromArray(['Tall row'], $style));
```

**Number format**

```
use SpoutX\Common\Entity\Style\NumberFormat;

$money = (new StyleBuilder())->setNumberFormat(new NumberFormat('#,##0.00'))->build();
// setFormat() is the shorthand: ->setFormat('#,##0.00')
```

---

Extra features (why this fork exists)
-------------------------------------

[](#extra-features-why-this-fork-exists)

These are only available in the XLSX writer. Get the current sheet with `$writer->getCurrentSheet()`.

**Merge cells**

```
$writer->getCurrentSheet()->mergeCells('A1:E1');
```

**Auto filter**

```
$writer->getCurrentSheet()->setAutoFilter('A2:E2');
```

**Column dimensions (width / auto-size / visibility)**

```
use SpoutX\Common\Entity\ColumnDimension;

$sheet = $writer->getCurrentSheet();
$sheet->addColumnDimension(new ColumnDimension('A', 30));        // fixed width 30
$sheet->addColumnDimension(new ColumnDimension('B', -1, true));  // auto-size
// signature: new ColumnDimension(string|int $columnIndex = 'A', float $width = -1, bool $autoSize = false, bool $visible = true)
```

**Comments**

```
use SpoutX\Writer\Common\Entity\Comment;

$sheet->addComment(new Comment('A2', 'A note', 'Author'));       // author is optional
```

`Comment` also exposes `setWidth()`, `setHeight()`, `setMarginLeft()`, `setMarginTop()`, `setVisible()` and `setStyle()`.

**Formulas with a precomputed value**

SpoutX does not evaluate formulas — you supply the value Excel should display until it recalculates.

```
$formula = new Cell('=B4*2');
$formula->setCalculatedValue('84');
$writer->addRow(WriterEntityFactory::createRow([$formula]));
```

**Sheets**

```
$sheet = $writer->getCurrentSheet();
$sheet->setName('Summary');
$sheet->setIsVisible(true);

$second = $writer->addNewSheetAndMakeItCurrent();  // returns the new Sheet
```

**Default row style &amp; writer options**

```
$writer->setDefaultRowStyle($someStyle);           // applied to rows without an explicit style
$writer->setShouldUseInlineStrings(true);          // XLSX: inline vs shared strings
$writer->setTempFolder('/custom/tmp');
```

---

Page setup, views, hyperlinks &amp; data validation
---------------------------------------------------

[](#page-setup-views-hyperlinks--data-validation)

These XLSX features were ported from OpenSpout v5 and adapted to SpoutX's mutable, per-sheet model. They are configured on the sheet (`$writer->getCurrentSheet()`).

**Print page setup**

```
use SpoutX\Writer\XLSX\Entity\PageSetup;
use SpoutX\Writer\XLSX\Entity\PageMargin;
use SpoutX\Writer\XLSX\Entity\HeaderFooter;
use SpoutX\Writer\XLSX\Entity\PageOrientation;
use SpoutX\Writer\XLSX\Entity\PaperSize;

$sheet->setPageSetup(new PageSetup(PageOrientation::Landscape, PaperSize::A4, fitToHeight: 1, fitToWidth: 1));
$sheet->setPageMargin(new PageMargin(top: 1.0, bottom: 1.0));
$sheet->setHeaderFooter(new HeaderFooter(oddHeader: '&CMy report', oddFooter: '&RPage &P of &N'));
```

**Freeze panes / sheet views**

```
use SpoutX\Writer\XLSX\Entity\SheetView;

// Freeze the first (header) row:
$sheet->setSheetView((new SheetView())->setFreezeRow(2));
// Freeze the first column:  ->setFreezeColumn('B')
// Zoom / gridlines:         (new SheetView())->setZoomScale(150)->setShowGridLines(false)
```

**Hyperlinks**

```
$sheet->addHyperlink('A1', 'https://example.com');
$sheet->addHyperlink('A2', 'mailto:hello@example.com');
```

**Data validation (dropdowns and constraints)**

```
use SpoutX\Writer\XLSX\Entity\DataValidation;
use SpoutX\Writer\XLSX\Entity\ValidationType;
use SpoutX\Writer\XLSX\Entity\ValidationOperator;

// Dropdown from a fixed list (values must not contain commas):
$sheet->addDataValidation(DataValidation::listFromValues('A2:A100', ['Yes', 'No', 'Maybe']));
// Dropdown backed by a cell range:
$sheet->addDataValidation(DataValidation::listFromRange('B2:B100', 'Lists!$A$1:$A$10'));
// Whole-number constraint with a custom error message:
$sheet->addDataValidation(new DataValidation(
    sqref: 'C2:C100',
    type: ValidationType::Whole,
    formula1: '1',
    formula2: '100',
    operator: ValidationOperator::Between,
    errorTitle: 'Out of range',
    error: 'Enter a number from 1 to 100',
));
```

Protection, visibility, properties &amp; rich text
--------------------------------------------------

[](#protection-visibility-properties--rich-text)

**Sheet &amp; workbook protection** (locks *editing*, optionally password-guarded)

```
use SpoutX\Writer\XLSX\Entity\SheetProtection;
use SpoutX\Writer\XLSX\Entity\WorkbookProtection;

$sheet->setSheetProtection(new SheetProtection(password: 'secret', lockSheet: true, lockSort: true));
$writer->setWorkbookProtection(new WorkbookProtection(password: 'secret', lockStructure: true)); // after openToFile()
```

**Tab visibility** (independent of protection). `lockStructure` above is what keeps a hidden tab hidden.

```
use SpoutX\Writer\XLSX\Entity\SheetVisibility;

$sheet->setVisibility(SheetVisibility::Hidden);      // unhideable by the user via the UI? no
$sheet->setVisibility(SheetVisibility::VeryHidden);  // not unhideable from the UI (only via code)
// $sheet->setIsVisible(false) is kept and maps to Hidden
```

**Document properties**

```
use SpoutX\Writer\XLSX\Entity\DocumentProperties;

$writer->setDocumentProperties(new DocumentProperties(
    title: 'Q1 Report', creator: 'Me', keywords: 'finance,q1', application: 'TBL Manager',
    customProperties: ['Department' => 'Finance', 'Reviewed' => 'yes'],
)); // after openToFile()
```

**Rich text** (multiple formats in one cell)

```
use SpoutX\Common\Entity\Cell;
use SpoutX\Common\Entity\RichText;
use SpoutX\Common\Entity\TextRun;
use SpoutX\Common\Entity\Style\Color;

use SpoutX\Common\Entity\TextRunVerticalAlignment;

$cell = new Cell(new RichText(
    new TextRun('Hello ', bold: true, fontColor: Color::RED),
    new TextRun('world', italic: true, fontSize: 14, fontName: 'Calibri'),
    new TextRun('2', verticalAlignment: TextRunVerticalAlignment::Superscript),
));
```

**Reading merge cells**

```
foreach ($reader->getSheetIterator() as $sheet) {
    $ranges = $sheet->getMergeCells();   // e.g. ['A1:C1', 'A3:A5']
}
```

---

Migrating from Box\\Spout / Spout
---------------------------------

[](#migrating-from-boxspout--spout)

- Namespace changed from `Box\Spout\…` to **`SpoutX\…`**.
- Package is **`wilsonglasser/spoutx`**; minimum PHP is **8.4**.
- **CSV and ODS are gone** — use `Type::XLSX` only.
- Several constant sets are now **backed enums** (breaking, but type-safe):
    - `Style::ALIGN_*` → `CellAlignment` (horizontal) and `CellVerticalAlignment` (vertical).
    - Border name/style/width strings → `BorderName` / `BorderStyle` / `BorderWidth`.
    - Cell type ints → `CellType`.
    - So `setHorizontalAlign(Style::ALIGN_RIGHT)` becomes `setHorizontalAlign(CellAlignment::Right)`, and `setBorderTop($color, 'thin', 'solid')` becomes `setBorderTop($color, BorderWidth::Thin, BorderStyle::Solid)`.
- The API is still **mutable** — `new Cell(...)`, `->setValue()`, `->setStyle()`, builder setters returning `$this`, etc., all behave as before.

---

Development
-----------

[](#development)

The dev environment runs in Docker (PHP 8.4 + ext-zip). Common tasks are wrapped in the `Makefile`:

```
make install    # composer install inside the container
make test       # run the PHPUnit suite
make cs         # php-cs-fixer dry-run (check)
make cs-fix     # php-cs-fixer fix
make shell      # open a shell in the container
```

The test suite includes a **golden-file characterization test** (`tests/SpoutX/CharacterizationTest.php`) that locks the exact XLSX XML output, plus a write→read roundtrip and a feature test covering the extras above.

---

License
-------

[](#license)

Licensed under the Apache License, Version 2.0. Originally Copyright Box, Inc.; fork maintained by Wilson Glasser.

###  Health Score

55

—

FairBetter than 97% of packages

Maintenance84

Actively maintained with recent releases

Popularity36

Limited adoption so far

Community21

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 69% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~130 days

Recently: every ~186 days

Total

20

Last Release

197d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/db61a757528a63bc3f14052511d5f7b4326af1b7f072b37d5c4896be49869516?d=identicon)[wilsonglasser](/maintainers/wilsonglasser)

---

Top Contributors

[![adrilo](https://avatars.githubusercontent.com/u/7086917?v=4)](https://github.com/adrilo "adrilo (234 commits)")[![wilsonglasser](https://avatars.githubusercontent.com/u/342362?v=4)](https://github.com/wilsonglasser "wilsonglasser (65 commits)")[![madflow](https://avatars.githubusercontent.com/u/183248?v=4)](https://github.com/madflow "madflow (15 commits)")[![sfichera](https://avatars.githubusercontent.com/u/3146077?v=4)](https://github.com/sfichera "sfichera (4 commits)")[![carusogabriel](https://avatars.githubusercontent.com/u/16328050?v=4)](https://github.com/carusogabriel "carusogabriel (3 commits)")[![Lewiscowles1986](https://avatars.githubusercontent.com/u/2605791?v=4)](https://github.com/Lewiscowles1986 "Lewiscowles1986 (2 commits)")[![KiNgMaR](https://avatars.githubusercontent.com/u/438358?v=4)](https://github.com/KiNgMaR "KiNgMaR (2 commits)")[![lichunqiang](https://avatars.githubusercontent.com/u/2433916?v=4)](https://github.com/lichunqiang "lichunqiang (1 commits)")[![nimmneun](https://avatars.githubusercontent.com/u/5374300?v=4)](https://github.com/nimmneun "nimmneun (1 commits)")[![philipbrown](https://avatars.githubusercontent.com/u/1579059?v=4)](https://github.com/philipbrown "philipbrown (1 commits)")[![rlukasz](https://avatars.githubusercontent.com/u/10188984?v=4)](https://github.com/rlukasz "rlukasz (1 commits)")[![someson](https://avatars.githubusercontent.com/u/3097223?v=4)](https://github.com/someson "someson (1 commits)")[![stianlik](https://avatars.githubusercontent.com/u/410251?v=4)](https://github.com/stianlik "stianlik (1 commits)")[![WebsourceCz](https://avatars.githubusercontent.com/u/676930?v=4)](https://github.com/WebsourceCz "WebsourceCz (1 commits)")[![welcoMattic](https://avatars.githubusercontent.com/u/773875?v=4)](https://github.com/welcoMattic "welcoMattic (1 commits)")[![camuthig](https://avatars.githubusercontent.com/u/5178217?v=4)](https://github.com/camuthig "camuthig (1 commits)")[![eusonlito](https://avatars.githubusercontent.com/u/644551?v=4)](https://github.com/eusonlito "eusonlito (1 commits)")[![yannickroger](https://avatars.githubusercontent.com/u/4035241?v=4)](https://github.com/yannickroger "yannickroger (1 commits)")[![garak](https://avatars.githubusercontent.com/u/179866?v=4)](https://github.com/garak "garak (1 commits)")[![glukkkk](https://avatars.githubusercontent.com/u/4921422?v=4)](https://github.com/glukkkk "glukkkk (1 commits)")

---

Tags

streamphpexcelxlsxcsvmemoryodfofficeOOXMLspreadsheetodsopenreadwritescale

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/wilsonglasser-spout/health.svg)

```
[![Health](https://phpackages.com/badges/wilsonglasser-spout/health.svg)](https://phpackages.com/packages/wilsonglasser-spout)
```

###  Alternatives

[openspout/openspout

PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way

1.2k75.4M296](/packages/openspout-openspout)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
