Getting Started

Public-routes mode

Get a working blog at /blog without writing controllers — opt in via config flags.

The package ships an opt-in public-routes mode. Flip a flag in config and you get:

RouteNameNotes
GET /blogblog.indexPaginated post listing
GET /blog/{slug}blog.showSingle post (only published)
GET /blog/category/{slug}blog.categoryCategory archive (paginated)
GET /blog/preview/{post}blog.previewSigned-only draft preview, with noindex,nofollow meta
GET /blog/feedblog.feedRSS 2.0 feed (route only registered when features.feed is true)
GET /blog/tag/{slug}blog.tagTag archive (gated by features.tags)

Enable it

config/ink.php
'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:

resources/views/layouts/app.blade.php
<!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:

config/ink.php
'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.feed off — the blog.feed route is never registered. Route::has('blog.feed') returns false.
  • features.tags off — the blog.tag route is still registered (so route('blog.tag', ...) keeps resolving without throwing), but tag() calls abort_unless($flag, 404) at request time. Route::has('blog.tag') returns true.

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

HeadlessPublic-routes
Routes registeredNoneAll 6
ControllersYou writeShipped
ViewsComponents onlyFull pages
Custom domain logicYes (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).