spatie/sheets
Spatie Sheets lets Laravel apps store and retrieve static content from plain text files. Markdown and front matter work out of the box, with flexible parsing, multiple content collections, indexing, and Eloquent-like casting—ideal for docs sites and blogs.
Installation:
composer require spatie/sheets
Publish the config (optional):
php artisan vendor:publish --provider="Spatie\Sheets\SheetsServiceProvider"
Configure Storage:
Edit config/sheets.php to define:
collections (e.g., ['posts', 'pages'])default_collection (e.g., 'posts')storage (e.g., 'local', 's3') with filesystem disk.First Use Case:
Place a Markdown file (e.g., storage/app/sheets/posts/hello.md) with front matter:
---
title: Hello World
slug: hello
---
# Welcome!
Retrieve it in a controller:
use Spatie\Sheets\Facades\Sheets;
public function show(Sheets $sheets) {
$sheet = $sheets->get('hello'); // Returns a `Sheet` model
return view('sheet', compact('sheet'));
}
Collection Management:
config/sheets.php to organize content (e.g., posts, docs).Sheets facade:
Sheets::collection('news')->get('latest');
File Parsing:
spatie/array-to-xml and spatie/markdown-toc).Spatie\Sheets\Parsers\Parser:
Sheets::extend('json', function () {
return new class extends Parser {
public function parse(string $path): array {
return json_decode(file_get_contents($path), true);
}
};
});
Caching:
config/sheets.php ('cache_enabled' => true) to avoid re-parsing files.php artisan sheets:clear-cache
Dynamic Routes:
Route::get('/posts/{sheet}', [SheetController::class, 'show']);
Register the binding in AppServiceProvider:
Sheets::bind();
Search/Indexing:
Sheets::all()->each(function ($sheet) {
$sheet->searchableData(); // Override in your Sheet model
});
Frontend Integration: Use Blade directives to render sheets:
@sheet('home')
<h1>{{ $sheet->title }}</h1>
{!! $sheet->contents !!} <!-- Auto-converts Markdown to HTML -->
@endsheet
(Requires registering the directive in AppServiceProvider.)
Validation:
Validate front matter in a Sheet model:
use Spatie\Sheets\Sheet;
class Post extends Sheet {
protected $rules = [
'title' => 'required|string|max:255',
'author' => 'required|string',
];
}
Testing:
Use Sheets::fake() to mock sheets in tests:
public function test_sheet_retrieval() {
Sheets::fake([
'home' => [
'title' => 'Test',
'contents' => '# Hello',
],
]);
$this->assertEquals('Test', Sheets::get('home')->title);
}
File Permissions:
storage/app/sheets) is writable by Laravel.Front Matter Parsing:
Symfony\Component\Yaml\Exception\ParseException.Caching Quirks:
php artisan sheets:clear-cache posts) won’t affect others.mtime. Changes to files may not reflect immediately if cached.Route Binding:
Sheet model’s getRouteKeyName() returns the correct key (e.g., slug):
public function getRouteKeyName() {
return $this->slug ?? parent::getRouteKeyName();
}
Markdown Processing:
parsedown/parsedown by default. For advanced Markdown (e.g., tables, footnotes), install spatie/markdown:
composer require spatie/markdown
Then configure in config/sheets.php:
'markdown' => [
'parser' => \Spatie\Markdown\Markdown::class,
],
Log Parsed Data:
Temporarily add this to your Sheet model to inspect parsed content:
protected static function boot() {
parent::boot();
static::retrieved(function ($sheet) {
\Log::debug('Parsed sheet:', [
'id' => $sheet->id,
'data' => $sheet->attributesToArray(),
]);
});
}
Check File Paths: Verify files are in the correct location:
\Log::info('Sheets storage path:', config('sheets.storage'));
Disable Parsing:
To debug raw file content, disable parsing in config/sheets.php:
'parse_contents' => false,
Custom Sheet Models:
Extend Spatie\Sheets\Sheet to add logic:
class BlogPost extends Sheet {
public function getExcerpt() {
return Str::limit($this->contents, 200);
}
}
Event Listeners:
Listen for sheet events (e.g., Retrieving, Retrieved):
Sheets::retrieved(function ($sheet) {
// Post-retrieval logic
});
Custom Storage:
Implement Spatie\Sheets\Storage\StorageInterface for non-filesystem storage (e.g., database):
class DatabaseStorage implements StorageInterface {
public function get(string $collection, string $id) {
return DB::table('sheets')->where('collection', $collection)->where('id', $id)->first();
}
// ... other methods
}
Register it in config/sheets.php:
'storage' => \App\Sheets\DatabaseStorage::class,
Middleware: Apply middleware to sheet retrieval:
Sheets::retrieving(function ($sheet) {
if ($sheet->isPrivate()) {
abort_unless(auth()->check(), 403);
}
});
How can I help you explore Laravel packages today?