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 (route only registered when features.feed is true) |
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 }}), point the views config map at views of your own — see the next section.
Customizing pages
Point the views config map at views your app already owns, per action:
'views' => [
'show' => 'blog.show',
'preview' => 'blog.preview',
],
Any key left null keeps rendering the package's own ink::pages.* view. See
Host-Owned Views for the full data each action passes and
for wiring the preview "edit this post" link.
Publishing (php artisan vendor:publish --tag=ink-views) also works — Laravel resolves
resources/views/vendor/ink/** ahead of the package's own views — but a published file is a
frozen copy that stops receiving upstream fixes. Prefer the views map.
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, but not always for the same reason:
features.feedoff — theblog.feedroute is never registered.Route::has('blog.feed')returnsfalse.features.tagsoff — theblog.tagroute is still registered (soroute('blog.tag', ...)keeps resolving without throwing), buttag()callsabort_unless($flag, 404)at request time.Route::has('blog.tag')returnstrue.
The tag route stays registered unconditionally so that route('blog.tag', ...) never throws
a RouteNotFoundException, even with the feature off — only the controller enforces the
flag.
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).