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 inventoryChoose 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 --panelsThe installer performs these application changes:
- creates
AdminPanelProviderunderapp/Providers/Inlay; - creates the default
UserResourceand 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 --productionThe 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-interactionDo 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/loginAfter 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 .vueThe 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 --generateThe 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 buildOn 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/mediafor the catalog, or installinlayphp/media-managerfor 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/importsfor the validated import pipeline. Keepinlayphp/tables-xlsxseparate because it is a heavy XLSX export adapter; install both in one command when the application needs both. - Security — install
inlayphp/two-factor-authenticationfor 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 buildThe command registers the Media Manager plugin, publishes its migrations, and creates the renderer page. Configure a persistent disk before production deployment:
INLAY_MEDIA_DISK=publicFor 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/validationuse 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:doctorRun the production check after the build:
php artisan inlay:doctor --productionThe 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:
confirm
npm run buildsucceeded;confirm the Vite manifest exists at
public/build/manifest.json;keep this source rule in
resources/css/app.css:@source '../../node_modules/@inlayphp/*/src/**/*.{ts,tsx,vue}';run
php artisan inlay:doctor --production;clear cached views/config with
php artisan optimize:clear;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 buildOpen /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 buildReview 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 buildcreates the Vite manifest and compiled CSS;php artisan inlay:doctor --productionreports 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.