panakour/filament-flat-page
Panakour Filament Flat Page is a Filament plugin for Laravel that lets you manage flat-file pages from the admin panel. Create, edit, and organize static content without a database, ideal for simple CMS-style pages, documentation, and lightweight site content.
Install the package and scaffold its assets:
composer require panakour/filament-flat-page
php artisan vendor:publish --tag="filament-flat-page-migrations"
php artisan migrate
If the package ships config/views, publish those too (--tag="filament-flat-page-config", --tag="filament-flat-page-views") — check the published tags list with php artisan vendor:publish since tags vary by release.
Register the plugin on the panel you want the admin UI to appear in, inside your PanelProvider::panel() chain:
->plugin(\Panakour\FilamentFlatPage\FlatPagePlugin::make())
Where to look first: the README.md install block (API surface changes between releases), then src/ for the Resource and Model classes, then the published migration to see exactly which columns you can fill. The migration is the real schema documentation for this kind of package.
First use case: log into /admin, open the Flat Pages resource, create a page with a slug (about, terms, contact), fill the content, save, then hit the corresponding frontend route. That round trip — admin form → DB row → rendered route — is the entire value proposition. Validate it works before designing anything around it.
Static-page-as-record. Treat flat pages as the replacement for hand-written Blade views for one-off content: about, privacy, terms, FAQ, landing pages. Seed them so every environment has the same slugs:
FlatPage::create(['slug' => 'about', 'title' => 'About us', 'content' => '...']);
Put seeds in a dedicated seeder and call it from DatabaseSeeder so CI and local match production.
Route + resolver pattern. Prefer a single catch-all route resolved by slug over one route per page:
Route::get('/{slug}', [FlatPageController::class, 'show'])
->where('slug', '[a-z0-9\-]+');
Return abort_unless($page, 404), and gate on a published/active flag if the package provides one. Keep the catch-all registered last so it never shadows real routes.
Layouts, not markup. Have the model expose the rendered content and let your app's layout own the chrome (nav, footer, analytics). Resist putting full <html> documents in the database — you lose version control and asset pipeline integration.
Cache the resolution. Flat pages are read-heavy and rarely written. Cache the lookup keyed by slug, and bust it in a model observer on saved/deleted:
static::saved(fn ($p) => Cache::forget("flat-page:{$p->slug}"));
If you deploy with route:cache/config:cache, this is the difference between "works in dev" and "works in prod."
Structured content. If the package supports repeatable blocks/sections rather than a single rich-text field, model your page as an ordered array of typed blocks and render with a @foreach + partial-per-type map. This keeps marketing able to reorder sections without a deploy.
Integration tip: consume the model directly anywhere you need the copy — footer blurbs, email templates, meta descriptions — rather than duplicating strings in lang/ files.
composer.json and check the README's install snippet against what you actually have installed; class names and plugin registration style drift.firstOrFail() behaviour. Exclude the current record with Rule::unique(...)->ignore($record) on edit./{slug} will happily claim login, api, admin. Keep an explicit deny-list or scope the route under a prefix like /p/{slug}.SoftDeletes, a forgotten cache entry can serve a deleted page. Bust cache on deleted and restored, not just saved.php artisan filament:optimize-clear if the resource doesn't appear. Run php artisan route:list | grep flat to confirm whether the package registers frontend routes itself or expects you to.php artisan tinker → FlatPage::whereSlug('about')->first() isolates "is it in the DB?" from "is the route binding it?" — check those separately before touching the view.How can I help you explore Laravel packages today?