PHPackages                             lekoala/baresheet - 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. lekoala/baresheet

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

lekoala/baresheet
=================

Fast, zero-dependency CSV, XLSX and ODS reader/writer for PHP

0.4.0(1mo ago)02.8k[1 PRs](https://github.com/lekoala/baresheet/pulls)1MITPHPPHP ^8.1.2CI passing

Since Mar 5Pushed 2w agoCompare

[ Source](https://github.com/lekoala/baresheet)[ Packagist](https://packagist.org/packages/lekoala/baresheet)[ GitHub Sponsors](https://github.com/lekoala)[ RSS](/packages/lekoala-baresheet/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (6)Dependencies (24)Versions (24)Used By (1)

Baresheet
=========

[](#baresheet)

Fast, zero-dependency CSV, XLSX, and ODS reader/writer for PHP.

[![License: MIT](https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667)](LICENSE)

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

[](#requirements)

- PHP 8.1+
- ext-mbstring (Required for all formats; Symfony polyfill is a valid alternative)

### Format Specific (Required for XLSX/ODS)

[](#format-specific-required-for-xlsxods)

- ext-zip
- ext-xmlreader, ext-simplexml, ext-libxml (standard XML extensions, usually bundled together)

### Optional

[](#optional)

- ext-iconv (Required only for CSV BOM transcoding)
- maennchen/zipstream-php (Required only for streaming XLSX/ODS output)

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

[](#installation)

```
composer require lekoala/baresheet
```

Quick Start
-----------

[](#quick-start)

```
use LeKoala\Baresheet\Baresheet;
use LeKoala\Baresheet\Options;

// Auto-detect format from extension
$rows = Baresheet::read('data.xlsx', new Options(assoc: true));
foreach ($rows as $row) {
    echo $row['email'];
}

// Write — format from extension
Baresheet::write($data, 'output.csv', new Options(bom: false));
Baresheet::write($data, 'output.xlsx', new Options(meta: ['creator' => 'My App']));
Baresheet::write($data, 'output.ods');

// Write to string
$csv = Baresheet::writeString($data, 'csv');
$xlsx = Baresheet::writeString($data, 'xlsx');
$ods = Baresheet::writeString($data, 'ods');

// Write to PHP resource (for PSR-7 or Laravel Responses)
$stream = Baresheet::writeStream($data, 'xlsx');

// Stream as download (sends HTTP headers)
Baresheet::output($data, 'report.xlsx');
```

Direct Reader/Writer Usage
--------------------------

[](#direct-readerwriter-usage)

Concrete classes allow setting properties directly or passing an `Options` object to the constructor:

```
use LeKoala\Baresheet\Options;
use LeKoala\Baresheet\CsvReader;
use LeKoala\Baresheet\CsvWriter;
use LeKoala\Baresheet\XlsxReader;
use LeKoala\Baresheet\XlsxWriter;

// CSV - Manual pattern
$reader = new CsvReader();
$reader->assoc = true;
$rows = $reader->readFile('data.csv');

// CSV - Options pattern
$writer = new CsvWriter(new Options(
    escapeFormulas: true,
));
$writer->writeFile($data, 'safe-export.csv');

// XLSX - Manual pattern
$reader = new XlsxReader();
$reader->sheet = 'Data';
$rows = $reader->readFile('report.xlsx');

// XLSX - Options pattern
$writer = new XlsxWriter(new Options(
    meta: ['creator' => 'My App'],
));
$writer->writeFile($data, 'report.xlsx');
```

Features
--------

[](#features)

### CSV

[](#csv)

- **Auto delimiter detection** — analyzes a sample to pick the best separator (default: `auto`)
- **BOM handling** — detects and natively transcodes UTF-8/16/32 BOM sequences on the fly via stream filters
- **Formula injection protection** — `escapeFormulas: true` (opt-in security flag, see Security section)
- **RFC 4180 compliant** — handles enclosures, double-quote escaping, and **CRLF (`\r\n`)** line endings by default for maximum interoperability.
- **Column Selection** — Efficiently select, reorder, and alias columns during read.
- **Stream reading** — `readStream()` for reading from any PHP resource

### XLSX

[](#xlsx)

- **Blazing fast reading** — optimized `XMLReader` with direct `zip://` streaming (2x faster than SimpleXLSX)
- **Data offset** &amp; **Empty line skipping** — safely skip arbitrary leading rows or completely empty lines
- **Extreme memory efficiency** — unified 0.63MB footprint regardless of file size
- **Column Selection** — Skip XML parsing for unselected cells for massive performance gains.
- **Shared string table** — opt-in de-duplication for smaller files (default: `false` for speed)
- **Auto column widths** — opt-in automatic column sizing (default: `false` for speed)
- **DateTime support** — pass `DateTimeInterface` objects directly, seamlessly handles 1900/1904 calendar systems
- **Freeze Pane &amp; Autofilter** — simple options for improved sheet usability
- **Document properties** — set creator, title, subject, keywords, etc. via `meta`

### ODS

[](#ods)

- **Streaming reader** — handles large files with minimal 0.63MB memory usage
- **Data offset** &amp; **Empty line skipping** — safely skip arbitrary leading rows or completely empty lines
- **Column Selection** — Skip XML parsing for unselected cells for significant performance gains.
- **Zero-dependency** — uses native `ZipArchive` + `XMLReader`
- **DateTime support** — dates stored accurately in ISO 8601
- **Document properties** — set creator and title via `meta`
- **Sheet selection** — read specific sheets by name or index

Options
-------

[](#options)

There are two ways to pass options:

**1. Directly on instances:**

```
$reader = new CsvReader();
$reader->assoc = true;
$reader->separator = ";";

// Or directly in the constructor
$reader = new CsvReader(new Options(assoc: true, separator: ";"));
```

**2. Options object** (works on any method, including the `Baresheet` facade). The constructor provides **full IDE autocomplete**:

```
use LeKoala\Baresheet\Options;

$opts = new Options(
    assoc: true,
    separator: 'auto',
    meta: ['creator' => 'My App']
);
$rows = Baresheet::read('data.csv', $opts);
```

OptionTypeDefaultApplies to`assoc`bool`false`Read (All)`strict`bool`false`Read (CSV, XLSX, ODS)`stream`bool`true`Output (Any)`limit`?int`null`Read (All)`offset`int`0`Read (All)`skipEmptyLines`bool`true`Read (All)`headers`string\[\]`[]`Write (All), Read (CSV)`separator`string`"auto"`Read (CSV)`enclosure`string`"`Read (CSV)`escape`string`""`Read (CSV)`eol`string`\r\n`Write (CSV)`inputEncoding`?string`null`Read (CSV)`outputEncoding`?string`null`Read (CSV)`bom`bool|string|Bom`true`Write (CSV)`escapeFormulas`bool/callable`false`Write (CSV)`meta`array/Meta`null`Write (XLSX, ODS)`autofilter`?string`null`Write (XLSX)`freezePane`?string`null`Write (XLSX)`sheet`string/int`null`Read/Write (XLSX, ODS)`boldHeaders`bool`false`Write (XLSX, ODS)`tempPath`?string`null`Any (Temp files location)`sharedStrings`bool`false`Write (XLSX)`autoWidth`bool`false`Write (XLSX)`requiredColumns`string\[\]`[]`Read (CSV, XLSX, ODS)`columns`string\[\]`[]`Read (CSV, XLSX, ODS)Required Columns Validation
---------------------------

[](#required-columns-validation)

Validate that input files contain expected columns before processing. This catches malformed files early, avoiding wasted cycles parsing invalid data.

```
// Throws RuntimeException if 'email' or 'price' columns are missing
$rows = Baresheet::read('products.csv', new Options(
    assoc: true,
    requiredColumns: ['sku', 'price', 'qty']
));

foreach ($rows as $row) {
    // All required columns are guaranteed to exist
    processProduct($row);
}
```

The validation occurs immediately after reading the header row and throws a descriptive exception listing the missing columns:

```
RuntimeException: Missing required columns: price, qty

```

Works with all reader formats (CSV, XLSX, ODS) and the `Baresheet` facade.

Column Selection
----------------

[](#column-selection)

Select and reorder specific columns when reading. This is useful for wide files where you only need a subset of columns, or when you need columns in a specific order. Selected columns must exist in the file headers (they are implicitly required).

```
// Select specific columns (assoc mode returns named array)
$rows = Baresheet::read('data.csv', new Options(
    assoc: true,
    columns: ['email', 'name']  // Only these columns, in this order
));

foreach ($rows as $row) {
    // $row contains only ['email' => '...', 'name' => '...']
}
```

### Reordering Columns

[](#reordering-columns)

Column selection also allows reordering:

```
// File has: name, email, age (in that order)
// Output: age first, then name
$rows = Baresheet::read('data.csv', new Options(
    assoc: true,
    columns: ['age', 'name']
));
```

### Plain Mode with Column Selection

[](#plain-mode-with-column-selection)

When using `assoc: false`, provide explicit headers and receive values in plain arrays:

```
$rows = Baresheet::read('data.csv', new Options(
    assoc: false,
    headers: ['email', 'name', 'age'],
    columns: ['name', 'email']
));

foreach ($rows as $row) {
    // $row contains: ['John', 'john@example.com'] (values only)
}
```

### Working with Headerless Files

[](#working-with-headerless-files)

When reading files without header rows, you can inject column names using the `headers` option. This enables column selection and associative output even for plain data files:

```
// File has no headers, just raw data:
// 1,John Doe,john@example.com,50000
// 2,Jane Smith,jane@example.com,60000

$rows = Baresheet::read('data.csv', new Options(
    headers: ['id', 'name', 'email', 'salary'],  // Define column structure
    columns: ['id', 'email', 'salary'],          // Select specific columns
    assoc: true                                  // Get named array output
));

foreach ($rows as $row) {
    // $row contains: ['id' => 1, 'email' => 'john@example.com', 'salary' => 50000]
}
```

This works with all reader formats (CSV, XLSX, ODS) and is useful when:

- Processing legacy data exports without headers
- Working with fixed-format data feeds
- Converting plain arrays to structured data

Column selection provides dramatic performance improvements for XLSX and ODS files by skipping XML parsing for unselected cells. For CSV, it provides a zero-overhead "direct indexing" fast path that avoids intermediate array allocations.

Format20 columns → 5 columns**Speedup****Memory/CPU Savings****XLSX**2.94s → 1.33s**~2.2x faster****High** (Skips XML Nodes)**ODS**1.80s → 1.25s**~1.4x faster****High** (Skips XML Nodes)**CSV**0.28s → 0.28s**Baseline****90%+** fewer hash-table entriesTip

**The CSV "Practical Ceiling"**: While CSV reading cannot skip bytes (as `fgetcsv` must tokenize every field to track quotes/delimiters), Baresheet uses a direct numeric indexing map. This avoids creating a full associative array for the entire row before subsetting, effectively reaching the maximum performance possible for column selection in PHP.

### Error Handling

[](#error-handling)

Missing columns throw immediately:

```
RuntimeException: Missing required columns: missing_column

```

### Data Transformation

[](#data-transformation)

Baresheet preserves raw cell values by design. For cleaning, casting, or filtering, use the `Transform` class — generator-based pipelines that compose with readers and writers without loading data into memory.

```
use LeKoala\Baresheet\Transform;

// Trim whitespace from all string values
$rows = Transform::trim($reader->readFile('data.csv'));

// Chain multiple transforms (PHP 8.5+ pipe operator)
$clean = $reader->readFile('data.csv')
    |> Transform::trim(...)
    |> Transform::nullAs(..., 'N/A')
    |> Transform::boolAs(..., 'Yes', 'No');

// Cast types for database inserts
$typed = Transform::cast($rows, [
    'qty' => 'int',
    'price' => 'float',
    'active' => 'bool',
    'created' => 'date',  // returns DateTimeInterface
]);

// Tell your IDE / PHPStan the expected shape after casting
/** @var Generator $typed */
$typed = Transform::cast($rows, [
    'qty' => 'int',
    'price' => 'float',
    'active' => 'bool',
    'created' => 'date',
]);

// Filter rows
$active = Transform::filter($rows, fn($row) => $row['active'] === 'Yes');

// Batch insert in chunks of 1000
foreach (Transform::chunk($rows, 1000) as $batch) {
    $db->bulkInsert($batch);
}
```

Streaming Output
----------------

[](#streaming-output)

For large files, streaming avoids writing a temporary file to disk. **Baresheet streams `output()` by default.**

However, keep in mind that **streaming changes how data is sent to the browser**. Because the total file size is unknown before the transfer starts, the server cannot send a `Content-Length` header. This means the browser download will not display a progress bar or an estimated time of completion.

To bypass streaming and force buffering, use `stream: false` with `output()`. Baresheet will buffer the file (either in memory for CSV, or via a temporary zip file for XLSX/ODS) to precisely calculate and send the `Content-Length` header along with it.

> **Note on XLSX/ODS:** Streaming requires an optional dependency. Install it with:

```
composer require maennchen/zipstream-php
```

If the `zipstream-php` dependency is missing, Baresheet will seamlessly and automatically fall back to buffered output.

```
$writer = new XlsxWriter();
$writer->stream = false;
$writer->output($data, 'report.xlsx');

// or via Options
Baresheet::output($data, 'report.xlsx', new Options(stream: false));
```

PSR-7 / Response Objects (Symfony, Laravel)
-------------------------------------------

[](#psr-7--response-objects-symfony-laravel)

To avoid breaking the flow of your application or sending explicit `header()` calls directly, you should create a Response object when applicable in your framework.

Use the `writeStream()` method to generate the spreadsheet as a memory-capped `php://temp` stream resource, and feed it into your Response class:

### Symfony / Laravel (StreamedResponse)

[](#symfony--laravel-streamedresponse)

```
use LeKoala\Baresheet\XlsxWriter;
use Symfony\Component\HttpFoundation\StreamedResponse;

$writer = new XlsxWriter();
$stream = $writer->writeStream($data);

return new StreamedResponse(function () use ($stream) {
    fpassthru($stream);
    fclose($stream);
}, 200, [
    'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'Content-Disposition' => 'attachment; filename="report.xlsx"',
]);
```

### PSR-7 (Guzzle, Nyholm, etc.)

[](#psr-7-guzzle-nyholm-etc)

```
$stream = Baresheet::writeStream($data, 'xlsx');
$body = new \GuzzleHttp\Psr7\Stream($stream); // wrap the native resource

return $response
    ->withHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
    ->withHeader('Content-Disposition', 'attachment; filename="report.xlsx"')
    ->withBody($body);
```

Performance
-----------

[](#performance)

Baresheet is explicitly engineered to minimize server resource footprint.

The XLSX and ODS readers use an optimized `XMLReader` approach that opens `zip://` streams directly. This avoids any temporary file extraction, cutting I/O overhead by 50% compared to standard zip extraction methods.

Here are the results extracting/writing 50,000 rows (4 columns) locally against other common industry standard libraries:

### Reading (Parsing) 50,000 Rows

[](#reading-parsing-50000-rows)

#### Reading CSV

[](#reading-csv)

LibraryAvg Time (s)Peak Memory (MB)Baresheet (CSV)0.00570.63League (CSV)0.00890.63OpenSpout (CSV)0.02010.63#### Reading XLSX

[](#reading-xlsx)

LibraryAvg Time (s)Peak Memory (MB)Baresheet (XLSX)0.03910.63SimpleXLSX (XLSX)0.08165.78OpenSpout (XLSX)0.21140.63#### Reading ODS

[](#reading-ods)

LibraryAvg Time (s)Peak Memory (MB)Baresheet (ODS)0.04840.63OpenSpout (ODS)0.15920.63### Writing 50,000 Rows

[](#writing-50000-rows)

#### Writing CSV

[](#writing-csv)

LibraryAvg Time (s)Peak Memory (MB)Baresheet (CSV)0.09560.50League (CSV)0.12880.55OpenSpout (CSV)0.27700.47#### Writing XLSX

[](#writing-xlsx)

LibraryAvg Time (s)Peak Memory (MB)Baresheet (XLSX)0.45040.79Baresheet (XLSX - Auto Width)0.44290.79SimpleXLSXGen (XLSX)0.6612109.77Baresheet (XLSX - Shared Strings)0.878034.26OpenSpout (XLSX)0.90111.01Baresheet (XLSX - Full)0.935134.26> Note: By default, Baresheet uses the fastest mode (shared strings and auto column width disabled). You can re-enable them via Options.

#### Writing ODS

[](#writing-ods)

LibraryAvg Time (s)Peak Memory (MB)Baresheet (ODS)0.76930.93OpenSpout (ODS)1.24740.88Security Considerations
-----------------------

[](#security-considerations)

### CSV Formula Injection

[](#csv-formula-injection)

When writing CSV files, any cell beginning with `=`, `+`, `-`, or `@` could be interpreted as a formula if the file is opened in spreadsheet software like Microsoft Excel. A maliciously crafted input could lead to execution of arbitrary functions or system commands on the user's local machine.

By default, Baresheet prioritizes **data round-trip integrity**. Attempting to automatically prefix formulas with a single quote (`'`) to disable formula execution corrupts otherwise valid user inputs.

If you are exporting data to be consumed by clients opening the file in Excel, you **must opt-in** to the protection logic:

```
$writer = new CsvWriter();
$writer->escapeFormulas = true; // Protects against formula injection by prefixing a single-quote
```

#### Selective Formula Escaping

[](#selective-formula-escaping)

For advanced use cases, `escapeFormulas` also accepts a **callable** that receives the cell value and column index, allowing you to selectively escape only specific columns:

```
$writer = new CsvWriter();
$writer->escapeFormulas = function (string $cell, int $colIndex): string {
    // Skip phone columns (column 1) to preserve + prefixes
    if ($colIndex === 1) {
        return $cell;
    }
    // Apply default escaping for everything else
    $chars = "=+-@\t\r";
    if ($cell !== '' && str_contains($chars, $cell[0])) {
        return "'" . $cell;
    }
    return $cell;
};
$writer->writeFile($data, 'export.csv');
```

**Important:** Heuristic detection of "malicious" formulas is fundamentally unreliable. Attackers can use `CHAR()` functions to build strings character-by-character, and new attack vectors emerge constantly. The library takes a conservative approach: blanket escaping by default when enabled, or user-controlled selective escaping via callback. For maximum security with user-generated content, prefer **XLSX** format, which has explicit cell type metadata and is immune to formula injection.

License
-------

[](#license)

MIT

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance95

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 60.4% 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 ~15 days

Total

6

Last Release

33d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/250762?v=4)[Thomas Portelange](/maintainers/lekoala)[@lekoala](https://github.com/lekoala)

---

Top Contributors

[![lekoala](https://avatars.githubusercontent.com/u/250762?v=4)](https://github.com/lekoala "lekoala (93 commits)")[![google-labs-jules[bot]](https://avatars.githubusercontent.com/in/842251?v=4)](https://github.com/google-labs-jules[bot] "google-labs-jules[bot] (61 commits)")

---

Tags

csvlightweightminimalodsphpspreadsheetxlsxphpexcelxlsxcsvwriterreaderspreadsheetodslightweight

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/lekoala-baresheet/health.svg)

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

###  Alternatives

[openspout/openspout

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

1.2k65.5M209](/packages/openspout-openspout)[avadim/fast-excel-reader

Lightweight and very fast XLSX Excel Spreadsheet and CSV Reader in PHP

106693.8k9](/packages/avadim-fast-excel-reader)

PHPackages © 2026

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