Docs / Inlay / Installation

Installation

This walks through a fresh composer require to publishing your first page, using a Hero block as the running example. It assumes an existing Laravel 11, 12, or 13 application.

1. Install the package

composer require wearepixel/inlay
composer require "livewire/livewire:^4.0"

Inlay's packaged admin is built on Livewire ^4.0, and Livewire is not a dependency Laravel ships with - install it alongside the package.

It's deliberately a suggest rather than a require, because rendering a composed page (<x-inlay::render>, Inlay::renderPage(), Inlay::pageRoutes()) never touches Livewire at all: a host application that only renders can skip it entirely. If you skip it and later call Inlay::adminRoutes() anyway, that call throws immediately and tells you so. Already on Livewire 3? Inlay conflicts with it - the admin resolves its own components through Livewire 4's namespaced component resolution, which Livewire 3 has no equivalent for.

2. Run the installer

php artisan inlay:install

This publishes config/inlay.php and runs php artisan migrate, creating the pages, page_sections, and page_revisions tables. If you'd rather do these individually:

php artisan vendor:publish --tag=inlay-config
php artisan migrate

3. Register the routes

Inlay registers no routes on its own, of any kind. You opt into each part explicitly, from your own routes/web.php:

use WeArePixel\Inlay\Facades\Inlay;

Inlay::adminRoutes();   // the admin - page index, editor, preview
Inlay::pageRoutes();    // published Pages, served publicly by Inlay itself
Inlay::routes();        // both of the above, as a convenience

adminRoutes() registers the admin under config('inlay.admin.prefix') (inlay by default, so /inlay/pages, /inlay/pages/create, and so on) with route names prefixed inlay.admin.. A bare visit to the prefix root (e.g. /inlay) redirects to the page index rather than 404ing. Visit /inlay/pages and you should see an empty page index - there's nothing to compose yet, because no block is registered.

Most applications need adminRoutes() plus either pageRoutes() or their own routes calling Inlay::renderPage() - rarely both, and never neither. See Public routing for the full reference.

4. Set your admin middleware

Open config/inlay.php:

'admin' => [
    'middleware' => ['web'],
],

Stop here before going further if your application has real content or real users. ['web'] alone means anyone who can reach your application can create, edit, and delete pages - there is no authentication or authorization built into the package itself, by design. The package is deliberately auth-agnostic; your application owns that decision. At minimum:

'admin' => [
    'middleware' => ['web', 'auth'],
],

If your application uses Laravel's default auth guard and has a login named route, this is enough to require a logged-in user. If not every authenticated user should be able to manage pages, layer in your own authorization:

'admin' => [
    'middleware' => ['web', 'auth', 'can:manage-pages'],
],

where manage-pages is a Gate::define() or policy ability your application defines - Inlay has no opinion on how you express it, it just needs a middleware name Laravel already understands.

This is entirely separate from config('inlay.pages.middleware'), which guards public Page requests instead - a visitor never needs an admin session to view a published Page, and an admin session is never required to serve one.

Why Livewire middleware persistence matters

The admin's every action after the first page load - adding a block, editing a setting, publishing - is dispatched to Livewire's own /livewire/update endpoint, not back through Inlay::adminRoutes(). Middleware that isn't explicitly told to persist across that boundary would otherwise guard only the initial page load, leaving every subsequent action unprotected for a session that's since been revoked or expired.

Inlay::adminRoutes() handles this for you: it resolves config('inlay.admin.middleware') to concrete middleware classes and registers them with Livewire as persistent middleware, so they re-run on every subsequent admin action too. You don't need to configure this separately - just make sure your real middleware is in config('inlay.admin.middleware') before Inlay::adminRoutes() runs.

5. Write your first block

A block is a normal Blade class component that implements InlayBlock - a marker interface with no required methods:

// app/Inlay/Blocks/Hero.php
namespace App\Inlay\Blocks;

use Illuminate\View\Component;
use Illuminate\View\View;
use WeArePixel\Inlay\Attributes\BlockMeta;
use WeArePixel\Inlay\Attributes\Text;
use WeArePixel\Inlay\Contracts\InlayBlock;

#[BlockMeta(label: 'Hero', category: 'Headers', description: 'A page heading with introductory copy.')]
final class Hero extends Component implements InlayBlock
{
    public function __construct(
        #[Text(label: 'Heading', maxLength: 80)]
        public string $heading = 'Welcome',

        #[Text(label: 'Subheading')]
        public string $subheading = '',
    ) {}

    public function render(): View
    {
        return view('blocks.hero');
    }
}
{{-- resources/views/blocks/hero.blade.php --}}
<section class="hero">
    <h1>{{ $heading }}</h1>
    @if ($subheading)
        <p>{{ $subheading }}</p>
    @endif
</section>

Register it by handle in config/inlay.php:

'blocks' => [
    'hero' => \App\Inlay\Blocks\Hero::class,
],

The handle (hero) is what gets persisted against a page's sections, never the class name directly - renaming or moving the class later doesn't break existing pages. Reload /inlay/pages and confirm the config picked it up:

php artisan config:clear   # only needed if config is cached in this environment

6. Configure the preview

The editor's preview pane is a real iframe pointed at your own frontend. Tell Inlay which of your views renders a composition:

// app/Providers/AppServiceProvider.php
use WeArePixel\Inlay\Facades\Inlay;

public function boot(): void
{
    Inlay::previewView('inlay-previews.default');
}
{{-- resources/views/inlay-previews/default.blade.php --}}
<x-app-layout>
    {{ $composition }}
</x-app-layout>

Your view receives:

  • $page - the WeArePixel\Inlay\Models\Page being previewed.
  • $composition - an Illuminate\Contracts\Support\Htmlable containing the rendered blocks. Echo it directly; {{ $composition }} is safe, since it's already-rendered HTML rather than raw user input.
  • $context - reserved for future use; currently always null.

If you skip this step, the preview pane still works - it falls back to a plain, clearly-labeled unstyled wrapper so you always know it isn't your real design yet.

7. Create and publish your first page

  1. Visit /inlay/pages and click New page. Give it a title. A public path is optional - leave it blank if you'd rather render this page through a route your own application already owns (option B below).
  2. In the editor, click + Add, choose Hero from the picker, and edit its heading in the inspector.
  3. Watch the preview pane update.
  4. Click Publish.

8. Render it on your actual site

Pick one of two options - see Public routing for the full reference.

Option A - let Inlay serve it publicly. Give the page a public path (e.g. about) in step 7, then register Inlay::pageRoutes():

// routes/web.php
Inlay::pageRoutes(prefix: 'pages'); // -> /pages/about
{{-- resources/views/inlay/pages/show.blade.php --}}
<x-app-layout>
    {{ $composition }}
</x-app-layout>

Configure this view path with config('inlay.pages.view') if you'd rather use a different name.

Option B - render it through a route you already own:

// routes/web.php
use WeArePixel\Inlay\Models\Page;

Route::get('/{page:path}', function (Page $page) {
    return view('pages.show', ['page' => $page]);
});
{{-- resources/views/pages/show.blade.php --}}
<x-app-layout>
    <x-inlay::render :page="$page" />
</x-app-layout>

Either way, visit the page's URL and you should see the published Hero block, styled by your own application's CSS - not Inlay's.

Next steps

Discovery Call

One call.
We'll both know.

20 minutes to walk through your project, ask the hard questions, and work out honestly if we're the right team for it.

  • One call per day - it gets our full attention
  • Australia's only Laravel Premier Partner
  • Trusted by Chemist Warehouse, HelloFresh and Youfoodz
  • Senior engineers only - no juniors on your project
  • Brisbane-based, onshore team

Press Esc to close  ·  B to reopen