User guide

Getting started

This chapter takes a clean Laravel application to a working Inlay panel. The generated code is application-owned, so you can inspect it immediately after the install.

Requirements

  • PHP 8.3 or newer;
  • Laravel 12 or 13;
  • Composer;
  • Node.js 20 or newer and npm, pnpm, Yarn, or Bun;
  • a database supported by your Laravel application.

Inlay's panel uses Inertia Laravel 3. React 19 is the default renderer. Vue 3 is available with the --renderer=vue preset.

Create a clean application

The Laravel installer can create a plain application without a starter kit:

laravel new inventory
cd inventory

Choose SQLite for a quick local installation, or configure MySQL/PostgreSQL in .env. Inlay does not replace Laravel's database or authentication contracts.

Install the panel

composer require inlayphp/inlay:"^0.3"
php artisan inlay:install --panels

The installer performs these application changes:

  • creates AdminPanelProvider under app/Providers/Inlay;
  • creates the default UserResource and list/create/edit page classes;
  • creates app/Validation/UserRules.php;
  • adds the Inertia root Blade view and request middleware when missing;
  • adds the official React renderer and Vite/Tailwind source configuration;
  • creates the panel login, dashboard, account settings, and Resource page wrappers;
  • registers the provider in config/inlay-panels.php;
  • redirects unauthenticated panel visitors to /admin/login;
  • leaves Media, roles, imports, and two-factor authentication opt-in.

Run the generated next steps:

php artisan migrate
php artisan inlay:make-user
npm install
npm run build
php artisan inlay:doctor --production

The user command prompts for a name, email, password, and confirmation. For a local demo only, options can be supplied non-interactively:

php artisan inlay:make-user \
  --name="Demo Admin" \
  --email="admin@example.com" \
  --password="password" \
  --no-interaction

Do not put a real production password in a shell command because it can remain in shell history. Use the interactive prompt or a deployment secret.

Open the panel:

http://localhost/admin/login

After authentication, the default panel contains:

  • Dashboard;
  • Users list, create, edit, and delete screens;
  • Account settings for the signed-in user;
  • global resource search;
  • responsive navigation, dark mode, and the default theme.

What gets generated

The installer writes ordinary application code. You own these files and can change them like any other Laravel code:

app/
  Inlay/
    Resources/
      UserResource.php
      ListUsers.php
      CreateUser.php
      EditUser.php
  Providers/Inlay/
    AdminPanelProvider.php
  Validation/
    UserRules.php
config/
  inlay-panels.php
resources/
  css/app.css
  js/
    layouts/inlay-panel-layout.tsx   # or .vue
    pages/inlay/
      auth/login.tsx                  # or .vue
      dashboard.tsx
    pages/users/
      index.tsx                       # or .vue
      form.tsx                        # or .vue

The PHP provider owns the panel URL, authentication middleware, navigation, theme, Resources, and plugins. The page wrappers are intentionally local so an application can add its own layout, slots, icons, and navigation without modifying a package.

Create your first Resource

Resources are the fastest path to model-backed CRUD. Generate one for a model you own:

php artisan make:inlay-resource Post --generate

The generator creates a Resource, list/create/edit page classes, and a validation class. It reads the model table once to create a useful starting point; it does not regenerate or overwrite your code on every request.

A small Resource usually looks like this:

<?php

namespace App\Inlay\Resources;

use App\Models\Post;
use App\Validation\PostRules;
use Inlay\Forms\Fields\Textarea;
use Inlay\Forms\Fields\TextInput;
use Inlay\Forms\Form;
use Inlay\Resources\Resource;
use Inlay\Tables\Columns\TextColumn;
use Inlay\Tables\Table;

final class PostResource extends Resource
{
    protected static string $model = Post::class;

    public static function form(Form $form): Form
    {
        return $form->schema([
            TextInput::make('title')->required()->maxLength(255),
            Textarea::make('excerpt')->columnSpan('full'),
        ]);
    }

    public static function table(Table $table): Table
    {
        return $table->columns([
            TextColumn::make('title')->searchable()->sortable(),
            TextColumn::make('created_at')->sortable(),
        ]);
    }

    public static function validation(): string
    {
        return PostRules::class;
    }
}

Register it on the generated provider:

return $panel->resources([
    UserResource::class,
    PostResource::class,
]);

The complete Resource API, lifecycle hooks, policies, soft deletes, relation managers, and custom pages are covered in Resources and CRUD.

Understand the request boundary

An Inlay screen has one server-owned path from request to browser:

Layer Responsibility
Laravel model and policy Data, scopes, authorization, and persistence
Resource, Form, or Table Fields, columns, filters, actions, and validation contract
Page class Route, operation, record, and mutation endpoint
Inertia Versioned props such as inlay.forms.v1 or inlay.tables.v1
React or Vue renderer Browser state, focus, keyboard behavior, and presentation

The renderer never becomes the source of authorization or business rules. A React and Vue page should receive the same PHP contract and produce the same allowed behavior.

Choose Vue instead of React

Vue is a first-class renderer. On a plain application:

php artisan inlay:install --panels --renderer=vue
npm install
npm run build

On a Laravel starter kit that already owns a Vue entrypoint, the installer preserves the existing application entrypoint and adds Inlay page wrappers. The PHP provider and route contract are identical; only the renderer imports and page file extensions differ.

Choose the package path

The inlayphp/inlay preset is the recommended starting point. It gives a new application the panel foundation and the pieces needed to build its first Forms, Tables, Resources, validation rules, actions, and widgets. You do not need to assemble those packages one by one.

Add larger product features only when the application needs them:

  • Media Manager — install inlayphp/media for the catalog, or install inlayphp/media-manager for the panel browser; Composer pulls the catalog in automatically when the manager is selected.
  • Permission Manager — install inlayphp/permission-manager; it already brings the Spatie adapter and Spatie Laravel Permission dependency. Composer reuses an existing compatible Spatie installation.
  • Imports and exports — install inlayphp/imports for the validated import pipeline. Keep inlayphp/tables-xlsx separate because it is a heavy XLSX export adapter; install both in one command when the application needs both.
  • Security — install inlayphp/two-factor-authentication for TOTP, recovery codes, and panel login challenges.

The lower-level packages (core, panels, support, ui, design, and theme) remain public for standalone pages and community extensions, but most applications receive them through the preset. See the complete package map when choosing a direct dependency.

Customize the panel

The generated provider is the application’s main panel composition point. A common customization keeps the same authentication and adds an application name, navigation groups, a Resource, and a theme:

use App\Inlay\Resources\PostResource;
use Inlay\Panel;
use Inlay\Theme\Theme;

public function panel(Panel $panel): Panel
{
    return $panel
        ->path('/admin')
        ->brandName('Acme')
        ->theme(Theme::default())
        ->navigationGroups(['Content', 'Administration'])
        ->resources([PostResource::class])
        ->globalSearch();
}

Keep panel registration in config/inlay-panels.php. Do not add the provider a second time to bootstrap/providers.php; Laravel discovers the package service provider and the panel registrar loads the configured application providers.

Useful installer options

--panels                    Install the panel preset (default).
--panel=reports             Use a different panel id and URL segment.
--renderer=react|vue|none   Select the frontend adapter.
--media                     Register the optional Media Manager.
--without-users             Do not generate UserResource.
--no-frontend               Generate PHP only.
--no-npm                    Update package files without running npm.
--force                     Replace generated application files intentionally.
--tenant-model=...          Generate a tenant-aware panel provider.

The installer is safe to rerun. It restores missing generated support files and does not replace application-owned providers, Resources, validation classes, or page wrappers unless --force is passed. Use --force only when you have reviewed the generated diff.

Add the optional Media Manager

Media is intentionally not part of the default panel. Add it when the application needs uploads, folders, albums, or a media picker:

php artisan inlay:install --panels --media
php artisan migrate
npm run build

The command registers the Media Manager plugin, publishes its migrations, and creates the renderer page. Configure a persistent disk before production deployment:

INLAY_MEDIA_DISK=public

For Laravel Cloud or another ephemeral filesystem, use an S3-compatible disk. See Plugins for authorization and storage details.

Use Forms and Tables without a panel

The panel is optional. A normal Inertia controller can return a Form or Table contract directly:

composer require inlayphp/forms inlayphp/tables inlayphp/actions inlayphp/validation
use Inlay\Tables\Columns\TextColumn;
use Inlay\Tables\Table;

Route::get('/reports', function () {
    return inertia('reports/index', [
        'table' => Table::make('reports')->columns([
            TextColumn::make('name')->searchable()->sortable(),
        ]),
    ]);
});

Use the React or Vue renderer that matches the application. See Standalone pages for route macros, base page classes, and submission handling.

Diagnose an installation

Run the lightweight check before a build:

php artisan inlay:doctor

Run the production check after the build:

php artisan inlay:doctor --production

The production check verifies the Vite manifest, compiled CSS, Tailwind source discovery, panel provider, renderer dependency, generated User Resource, and optional Media files when Media is installed.

If the browser shows an unstyled page:

  1. confirm npm run build succeeded;

  2. confirm the Vite manifest exists at public/build/manifest.json;

  3. keep this source rule in resources/css/app.css:

    @source '../../node_modules/@inlayphp/*/src/**/*.{ts,tsx,vue}';
  4. run php artisan inlay:doctor --production;

  5. clear cached views/config with php artisan optimize:clear;

  6. hard-refresh the browser.

If /admin/users is blank, inspect the browser console and the Inertia props. The page must receive an inlayPanel prop when rendered inside a panel. A stale split package or a custom page resolver that drops shared props is a common cause.

Test the first screen

Before adding application-specific behavior, verify the generated panel:

php artisan migrate
php artisan inlay:make-user
php artisan inlay:doctor --production
vendor/bin/pest --compact
npm run build

Open /admin/login, sign in with the user created by inlay:make-user, and check the dashboard, Users Resource, account settings, global search, and logout flow. When a screen is blank, inspect the network response and browser console before changing the query or migration.

Add a panel to an existing application

Inlay does not require a new Laravel application. On an existing app:

composer require inlayphp/inlay:"^0.3"
php artisan inlay:install --panels --no-npm
npm install
npm run build

Review the generated provider and config/inlay-panels.php. If the application already has authentication, Inlay reuses the configured Laravel guard. If it already has a React or Vue entrypoint, keep the application's providers and merge the generated Inlay page resolver instead of replacing unrelated pages.

Production checklist

Before deploying a panel, verify:

  • APP_KEY, APP_URL, and the authentication guard are configured;
  • migrations run with php artisan migrate --force;
  • npm run build creates the Vite manifest and compiled CSS;
  • php artisan inlay:doctor --production reports ready;
  • policies and panel access rules cover every Resource and action;
  • queues run when imports, notifications, media transformations, or widgets are asynchronous;
  • Media Manager uses persistent storage such as S3 in an ephemeral runtime;
  • demo credentials and local-only passwords are removed;
  • the Composer and frontend lockfiles are committed.

What the installer does not decide

The installer creates a safe starting point, not application policy. You still decide:

  • which users may access the panel;
  • which Resource records are visible to each tenant;
  • which Laravel policies authorize actions;
  • which validation rules apply to your domain;
  • whether to use Spatie permissions, the permission plugin, or policies alone;
  • which storage disk and retention policy apply to uploaded files;
  • which theme tokens represent your brand.

That separation is deliberate: generated code is easy to understand and the packages remain independently useful.