PHPackages                             spatie/sheets - 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. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. spatie/sheets

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

spatie/sheets
=============

Store &amp; retrieve your static content in plain text files

1.12.3(2mo ago)30187.7k↓22%233MITPHPPHP ^8.0CI failing

Since May 3Pushed 2mo ago5 watchersCompare

[ Source](https://github.com/spatie/sheets)[ Packagist](https://packagist.org/packages/spatie/sheets)[ Docs](https://github.com/spatie/sheets)[ Fund](https://spatie.be/open-source/support-us)[ RSS](/packages/spatie-sheets/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (9)Versions (37)Used By (3)

Store &amp; retrieve your static content in plain text files
============================================================

[](#store--retrieve-your-static-content-in-plain-text-files)

[![Latest Version on Packagist](https://camo.githubusercontent.com/acc893665f41ed3d3d563cd0481ec83db57d12313dbaadca18b0c5f153ddefab/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7370617469652f7368656574732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/spatie/sheets)[![Total Downloads](https://camo.githubusercontent.com/7f2a4c159655c21f2d87adce2594e22554f1c74173ffc287aa19b1c3b2b2a858/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7370617469652f7368656574732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/spatie/sheets)

Sheets is a Laravel package to store, retrieve &amp; index content stored as text files. Markdown &amp; front matter are supported out of the box, but you can parse &amp; extract data from your files in whatever format you prefer.

Sheets can be added to any existing Laravel application and is a perfect fit for documentation sites &amp; personal blogs.

```
---
title: Home
---
# Hello, world!

Welcome to Sheets!
```

```
class SheetController
{
    public function index(Sheets $sheets)
    {
        return view('sheet', [
            'sheet' => $sheets->get('home'),
        ]);
    }

    public function show(string $id, Sheets $sheets)
    {
        return view('sheet', [
            'sheet' => $sheets->get($id),
        ]);
    }
}
```

```
@extends('layouts.app', [
    'title' => $sheet->title,
])

@section('main')
    {{ $sheet->contents }}
@endsection
```

Features
--------

[](#features)

- Allows any document format (by default Markdown files with front matter)
- Stores your content wherever you want (uses Laravel's filesystem component)
- Keeps multiple collections of content (e.g. posts, pages, etc.)
- Casts your document contents to Eloquent-like classes with accessors
- Convention over configuration, near-zero setup if you use the defaults

Support us
----------

[](#support-us)

[![](https://camo.githubusercontent.com/e9ae7cb6fad351b4ba2b0c35aaf50fdae446f120eb0dac272de60caf9df961fe/68747470733a2f2f6769746875622d6164732e73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f7368656574732e6a70673f743d31)](https://spatie.be/github-ad-click/sheets)

We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).

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

[](#installation)

You can install the package via composer:

```
composer require spatie/sheets
```

Laravel will auto-discover and register the `SheetsServiceProvider`, so no further setup is required.

After installing, you must publish the `sheets.php` configuration file:

```
php artisan vendor:publish --provider="Spatie\Sheets\SheetsServiceProvider" --tag="config"

```

Finally you must [create your first collection](https://github.com/spatie/sheets#configuring-collections).

Usage
-----

[](#usage)

The `Sheets` instance is available through a facade, helper function, or dependency injection.

```
use Sheets;

Sheets::all();
```

```
sheets()->all();
```

```
use Spatie\Sheets\Sheets;

class SheetsController
{
    public function index(Sheets $sheets)
    {
        return view('sheets', [
            'sheets' => $sheets->all(),
        ]);
    }
}
```

### Creating your first collection

[](#creating-your-first-collection)

A collection maps to a folder in your filesystem of choice. Sheets will look for a disk configured in `config/filesystems.php` with the same name as the collection—or you can [configure the disk name yourself](#disk).

```
// config/filesystems.php
return [
    'disks' => [
        // ...
        'posts' => [
            'driver' => 'local',
            'root' => base_path('posts'),
        ],
    ],
];

// config/sheets.php
return [
    'collections' => ['posts'],
];
```

Sheets will create a repository for the `posts` folder in your application.

```
app/
config/
posts/
  hello-world.md

```

```
---
title: Hello, world!
---
# Hello, world!

Welcome to Sheets!

```

A repository has two public methods: `all()` and `get($slug)`. You can get a repository instance through the `collection` method on `Sheets`.

`Repository::all()` will return an `Illuminate\Support\Collection` containing `Spatie\Sheets\Sheet` instances.

```
$repository = Sheets::collection('posts');

$repository->all();
```

`Repository::get($slug)` returns a single `Sheet` instance or `null` if nothing was found. A sheet's `slug` field contains its filename without an extension.

```
Sheets::collection('posts')->get('hello-world');
```

A `Sheet` instance is very similar to an Eloquent model. It holds an array of attributes that are exposed as properties. By default it will contain the path as a `slug` field, all front matter data, and a `contents` field containing an HTML representation of the contained Markdown.

```
$sheet = Sheets::collection('posts')->get('hello-world');

echo $sheet->slug;
// 'hello-world'

echo $sheet->title;
// 'Hello, world!'

echo $sheet->contents;
// 'Hello, world!Welcome to Sheets!'
```

You can create your own `Sheet` implementations with accessors just like Eloquent, but we'll [dive into that later](#sheet-class).

### Configuring collections

[](#configuring-collections)

Sheets is highly configurable. You can configure each collection separately by using an associative array in `config/sheets.php`.

```
// config/sheets.php
return [
    'collections' => [
        'pages' => [
            'disk' => null, // Defaults to collection name
            'sheet_class' => Spatie\Sheets\Sheet::class,
            'path_parser' => Spatie\Sheets\PathParsers\SlugParser::class,
            'content_parser' => Spatie\Sheets\ContentParsers\MarkdownWithFrontMatterParser::class,
            'extension' => 'md',
        ],
    ],
];
```

Above is what a collection's default configuration looks like (the configuration we've been working until now). When configuring a collection, every key is optional, if it doesn't exist, Sheets will use one of these values.

#### Disk

[](#disk)

The disk name where your content is stored. Disks are configured in `config/filesystems.php`. By default, Sheets will look for a disk with the same name as the collection.

```
// config/sheets.php
return [
    'collections' => [
        'pages' => [
            'disk' => null, // Uses the 'pages' disk
        ],
    ],
];
```

```
// config/sheets.php
return [
    'collections' => [
        'pages' => [
            'disk' => 'sheets', // Uses the 'sheets' disk
        ],
    ],
];
```

#### Sheet class

[](#sheet-class)

Your content will be casted to `Sheet` instances. The `Sheet` class is similar to a trimmed-down Eloquent model: it holds a set of attributes that are available as properties.

```
$sheet = Sheets::collection('page')->get('hello-world');

echo $sheet->slug;
// 'hello-world'
```

You can extend the `Sheet` class to add accessors (just like [in Eloquent](https://laravel.com/docs/5.6/eloquent-mutators#accessors-and-mutators)) and custom behavior.

```
namespace App;

use Spatie\Sheets\Sheet;

class Page extends Sheet
{
    public function getUrlAttribute(): string
    {
        return url($this->slug);
    }
}
```

```
// config/sheets.php
return [
    'collections' => [
        'pages' => [
            'sheet_class' => App\Page::class,
        ],
    ],
];
```

```
$sheet = Sheets::collection('pages')->get('hello-world');

echo $sheet->url;
// 'https://example.app/hello-world'
```

#### Path parser

[](#path-parser)

Sheets uses the file path to determine part of the `Sheet` attributes. A path parser is able to parse the path to a set of attributes.

The default path parser is the `SlugParser`, which simply adds a `slug` attribute based on the file name.

```
namespace Spatie\Sheets\PathParsers;

use Spatie\Sheets\PathParser;

class SlugParser implements PathParser
{
    public function parse(string $path): array
    {
        return ['slug' => explode('.', $path)[0]];
    }
}
```

You can customize the collection's path parser with the `path_parser` option.

```
// config/sheets.php
return [
    'collections' => [
        'posts' => [
            'path_parser' => Spatie\Sheets\PathParsers\SlugWithDateParser::class,
        ],
    ],
];
```

Above, we configured the path parser for `posts` to the `SlugWithDateParser`, which allows you to prefix your filenames with a date. This is useful for time-sensitive content like blogs.

```
posts/
  2018-05-05.my-first-post.md

```

The above sheet will have two attributes: a `date` containing an `Illuminate\Support\Carbon` instance, and a `slug` `my-first-post`.

You can write your own path parsers by implementing the `Spatie\Sheets\PathParser` interface. Path parsers are instantiated through Laravel's container, so you can inject it's dependencies via the `__construct` method if desired.

#### Content Parser

[](#content-parser)

Content parsers are similar to path parsers, but are in charge of parsing the file's contents.

The default content parser is the `MarkdownWithFrontMatterParser`, which extracts front matter and transforms Markdown to HTML.

```
class MarkdownWithFrontMatterParser implements ContentParser
{
    /** @var \League\CommonMark\CommonMarkConverter */
    protected $commonMarkConverter;

    public function __construct(CommonMarkConverter $commonMarkConverter)
    {
        $this->commonMarkConverter = $commonMarkConverter;
    }

    public function parse(string $contents): array
    {
        $document = YamlFrontMatter::parse($contents);

        return array_merge(
            $document->matter(),
            ['contents' => $this->commonMarkConverter->convert($document->body())]
        );
    }
}
```

You can customize the collection's content parser with the `content_parser` option.

```
// config/sheets.php
return [
    'collections' => [
        'pages' => [
            'content_parser' => Spatie\Sheets\ContentParsers\MarkdownParser::class,
        ],
    ],
];
```

Above, we configured the path parser for `pages` to the `MarkdownParser`, which parses Markdown files *without* front matter.

You can write your own content parsers by implementing the `Spatie\Sheets\ContentParser` interface. Content parsers are instantiated through Laravel's container, so you can inject it's dependencies via the `__construct` method if desired.

#### Extension

[](#extension)

The file extension used in a collection. Defaults to `md`.

#### Default collections

[](#default-collections)

You can call `get` or `all` on the `Sheets` instance without specifying a collection first to query the default collection.

```
// Return all sheets in the default collection
Sheets::all();
```

You can specify a default collection in `sheets.config`. If no default collection is specified, the default collection will be the first collection registered in the `collections` array.

Here the default collection will implicitly be set to `pages`:

```
return [
    'default_collection' => null,

    'collections' => [
        'pages',
    ],
];
```

Below the default collection is set to `pages`:

```
return [
    'default_collection' => 'pages',

    'collections' => [
        'posts',
        'pages',
    ],
];
```

### Route model binding

[](#route-model-binding)

You can register custom route resolution logic to immediately inject `Sheet` instances in your controller actions.

```
// app/Providers/RouteServiceProvider.php

public function boot()
{
    parent::boot();

    Route::bind('sheet', function ($path) {
        return $this->app->make(Spatie\Sheets\Sheets::class)
            ->get($path) ?? abort(404);
    });
}
```

Now the router will resolve any `sheet` parameter to a `Sheet` object.

```
Route::get('/{sheet}', 'SheetsController@show');

class SheetsController
{
    public function show(Sheet $sheet)
    {
        return view('sheet', ['sheet' => $sheet]);
    }
}
```

It might be useful to register specific bindings for other collections.

```
use Spatie\Sheets\Sheets;

Route::bind('post', function ($path) {
    return $this->app->make(Sheets::class)
        ->collection('posts')
        ->get($path) ?? abort(404);
});
```

The Laravel docs have an entire section on [Route Model Binding](https://laravel.com/docs/5.6/routing#route-model-binding).

### Use subdirectories to organize your sheets

[](#use-subdirectories-to-organize-your-sheets)

When you want to organize your sheets using (sub)directories, you need to define the route parameter to accept all characters. This way the complete relative path for your sheets will be sent to the controller.

```
Route::get('/', 'SheetsController@index')->where('sheet', '(.*)');
Route::get('{sheet}', 'SheetsController@show')->where('sheet', '(.*)');
```

### Testing

[](#testing)

```
composer test
```

### Changelog

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.

### Security

[](#security)

If you've found a bug regarding security please mail  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Sebastian De Deyne](https://github.com/sebastiandedeyne)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

64

—

FairBetter than 99% of packages

Maintenance83

Actively maintained with recent releases

Popularity50

Moderate usage in the ecosystem

Community31

Small or concentrated contributor base

Maturity80

Battle-tested with a long release history

 Bus Factor2

2 contributors hold 50%+ of commits

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 ~84 days

Recently: every ~182 days

Total

35

Last Release

86d ago

Major Versions

0.3.1 → 1.0.02018-05-31

PHP version history (6 changes)0.1.0PHP ^7.1

1.3.3PHP ^7.1.3

1.5.0PHP ^7.2

1.6.1PHP ^7.3

1.7.2PHP ^7.3|^8.0

1.7.3PHP ^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/7535935?v=4)[Spatie](/maintainers/spatie)[@spatie](https://github.com/spatie)

---

Top Contributors

[![sebastiandedeyne](https://avatars.githubusercontent.com/u/1561079?v=4)](https://github.com/sebastiandedeyne "sebastiandedeyne (55 commits)")[![freekmurze](https://avatars.githubusercontent.com/u/483853?v=4)](https://github.com/freekmurze "freekmurze (49 commits)")[![Gummibeer](https://avatars.githubusercontent.com/u/6187884?v=4)](https://github.com/Gummibeer "Gummibeer (16 commits)")[![AdrianMrn](https://avatars.githubusercontent.com/u/12762044?v=4)](https://github.com/AdrianMrn "AdrianMrn (8 commits)")[![laravel-shift](https://avatars.githubusercontent.com/u/15991828?v=4)](https://github.com/laravel-shift "laravel-shift (6 commits)")[![riasvdv](https://avatars.githubusercontent.com/u/3626559?v=4)](https://github.com/riasvdv "riasvdv (4 commits)")[![alexjoffroy](https://avatars.githubusercontent.com/u/7209163?v=4)](https://github.com/alexjoffroy "alexjoffroy (4 commits)")[![Nielsvanpach](https://avatars.githubusercontent.com/u/10651054?v=4)](https://github.com/Nielsvanpach "Nielsvanpach (4 commits)")[![markvaneijk](https://avatars.githubusercontent.com/u/1925388?v=4)](https://github.com/markvaneijk "markvaneijk (3 commits)")[![AlexVanderbist](https://avatars.githubusercontent.com/u/6287961?v=4)](https://github.com/AlexVanderbist "AlexVanderbist (3 commits)")[![patinthehat](https://avatars.githubusercontent.com/u/5508707?v=4)](https://github.com/patinthehat "patinthehat (3 commits)")[![thecaliskan](https://avatars.githubusercontent.com/u/13554944?v=4)](https://github.com/thecaliskan "thecaliskan (2 commits)")[![mbabker](https://avatars.githubusercontent.com/u/368545?v=4)](https://github.com/mbabker "mbabker (1 commits)")[![blueclock](https://avatars.githubusercontent.com/u/586174?v=4)](https://github.com/blueclock "blueclock (1 commits)")[![willemvb](https://avatars.githubusercontent.com/u/1336390?v=4)](https://github.com/willemvb "willemvb (1 commits)")[![peter279k](https://avatars.githubusercontent.com/u/9021747?v=4)](https://github.com/peter279k "peter279k (1 commits)")[![akr4m](https://avatars.githubusercontent.com/u/17238742?v=4)](https://github.com/akr4m "akr4m (1 commits)")[![akoepcke](https://avatars.githubusercontent.com/u/5311185?v=4)](https://github.com/akoepcke "akoepcke (1 commits)")[![svenluijten](https://avatars.githubusercontent.com/u/11269635?v=4)](https://github.com/svenluijten "svenluijten (1 commits)")[![vladunguru](https://avatars.githubusercontent.com/u/19543943?v=4)](https://github.com/vladunguru "vladunguru (1 commits)")

---

Tags

bloglaravelmarkdownspatielaravelsheets

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/spatie-sheets/health.svg)

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

###  Alternatives

[graham-campbell/markdown

Markdown Is A CommonMark Wrapper For Laravel

1.3k7.1M64](/packages/graham-campbell-markdown)[tightenco/jigsaw

Simple static sites with Laravel's Blade.

2.2k438.5k29](/packages/tightenco-jigsaw)[spatie/laravel-markdown

A highly configurable markdown renderer and Blade component for Laravel

4053.4M35](/packages/spatie-laravel-markdown)[spatie/laravel-google-cloud-storage

Google Cloud Storage filesystem driver for Laravel

2408.9M13](/packages/spatie-laravel-google-cloud-storage)[spatie/laravel-health

Monitor the health of a Laravel application

86910.0M83](/packages/spatie-laravel-health)[spatie/laravel-livewire-wizard

Build wizards using Livewire

4061.0M4](/packages/spatie-laravel-livewire-wizard)

PHPackages © 2026

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