Public-routes mode
The package ships an opt-in public-routes mode. Flip a flag in config and you get:
| Route | Name | Notes |
|---|---|---|
GET /blog | blog.index | Paginated post listing |
GET /blog/{slug} | blog.show | Single post (only published) |
GET /blog/category/{slug} | blog.category | Category archive (paginated) |
GET /blog/preview/{post} | blog.preview | Signed-only draft preview, with noindex,nofollow meta |
GET /blog/feed | blog.feed | RSS 2.0 feed (gated by features.feed) |
GET /blog/tag/{slug} | blog.tag | Tag archive (gated by features.tags) |
Enable it
'features' => [
'public_routes' => true,
'feed' => true, // optional, enables /blog/feed
'tags' => true, // optional, enables /blog/tag/{slug}
],
'layout' => 'layouts.app', // your host layout the page views extend
That's it. The service provider registers the routes at boot — no Filament panel boot is required, so the public site keeps working for guests who never touch the admin.
Required: a host layout
The page views extend the layout you set in 'layout'. It must define a @yield('content') slot. A minimal example:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ $title ?? config('app.name') }}</title>
@stack('head')
</head>
<body class="bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100">
@yield('content')
</body>
</html>
If your layout uses a different slot mechanism (e.g. Blade components with {{ $slot }}), publish the page views and adapt them:
php artisan vendor:publish --tag=filament-blog-views
Customizing pages
The shipped pages live at:
resources/views/vendor/blog/pages/index.blade.phpresources/views/vendor/blog/pages/show.blade.phpresources/views/vendor/blog/pages/category.blade.phpresources/views/vendor/blog/pages/preview.blade.phpresources/views/vendor/blog/pages/tag.blade.phpresources/views/vendor/blog/pages/feed.blade.php
Edit them freely — once published, the package no longer serves its own copies.
Custom prefix
Change 'prefix' => 'blog' in config. All routes pick up the new prefix.
Disabling individual pieces
Each feature flag is independent:
'features' => [
'public_routes' => true,
'feed' => false, // no RSS feed
'tags' => false, // no tag archive (admin still works if registered)
],
When a flag is off, requests to that path return 404 (not registered). Route::has(...) returns true (the route is defined) but the controller calls abort_unless($flag, 404). That's a deliberate choice so route ordering stays predictable and you can probe the route name without crashing.
Mode comparison
| Headless | Public-routes | |
|---|---|---|
| Routes registered | None | All 6 |
| Controllers | You write | Shipped |
| Views | Components only | Full pages |
| Custom domain logic | Yes (any) | Limited to view overrides |
| Effort to ship a blog | ~2 hours | ~5 minutes |
If you outgrow public-routes mode, flip the flag back to false and write your own controllers — see Frontend Setup (headless).