User guide
Plugins and optional features
The clean panel stays small. Larger concerns are official plugins that use the same Panel, Form, Table, Action, Validation, and Theme contracts. Install only what the applicatio…
The plugin pattern
return $panel->plugins([
PermissionManagerPlugin::make(),
MediaManagerPlugin::make(),
]);Each plugin can contribute routes, abilities, navigation, assets, widgets, and renderer page keys. A plugin should have a unique ID and register in PHP; it should not patch the generated frontend shell by string replacement.
For a custom plugin:
final class AuditPlugin implements Plugin
{
public function id(): string
{
return 'acme.audit';
}
public function register(PluginContext $context): void
{
$panel = $context->hostAs(Panel::class);
$panel->abilities([
AbilityDefinition::make('audit.view')->label('View audit log'),
]);
}
public function boot(PluginContext $context): void
{
// Resolve services that are available after registration.
}
}Registration is atomic and duplicate extension ownership is rejected.
Permissions and roles
Install the standalone access plugin:
composer require inlayphp/permission-manager
php artisan vendor:publish --provider="Spatie\\Permission\\PermissionServiceProvider"
php artisan migrate
php artisan inlay:permissions:sync
npm install @inlayphp/permission-manager-reactThe Composer package already requires inlayphp/authorization-spatie and
spatie/laravel-permission. You do not need to detect or list those packages
separately; Composer reuses a compatible version when the application already
has one installed.
Add Spatie's trait to the authenticatable model:
use Spatie\Permission\Traits\HasRoles;
final class User extends Authenticatable
{
use HasRoles;
}Register the plugin after application Resources:
return $panel
->resources([
UserResource::class,
OrderResource::class,
])
->plugin(PermissionManagerPlugin::make());It adds roles, permissions, user-role assignment, and an ability audit. Laravel Gate and policies remain authoritative. Roles are bundles of abilities, not a replacement for contextual policy checks.
Synchronize after adding Resources or plugins:
php artisan inlay:permissions:sync
php artisan inlay:permissions:sync --dry-run
php artisan inlay:permissions:sync --prune --forceReview dry-run output before pruning stale permissions. Keep guards consistent;
web roles do not automatically apply to another guard.
Media catalog and manager
The catalog can be used without a panel:
composer require inlayphp/media
php artisan vendor:publish --tag=inlay-media-migrations
php artisan migrateThe catalog owns assets, folders, collections, visibility, trash, restore, storage metadata, and transformers. It does not own routes or UI.
For the panel browser:
composer require inlayphp/media-manager
php artisan vendor:publish --tag=inlay-media-migrations
php artisan migrate
npm install @inlayphp/media-manager-reactInstalling inlayphp/media-manager automatically installs its required
inlayphp/media catalog package. Install inlayphp/media alone only when you
want the storage/catalog services without panel routes or UI.
Register it explicitly:
return $panel
->resourceMutationMiddleware(['throttle:media'])
->plugin(MediaManagerPlugin::make());Grant the contributed abilities through Gate, policies, or the optional Spatie adapter:
media.viewAny, media.pick, media.upload, media.update
media.delete, media.restore, media.forceDelete, media.download
media.manageFolders, media.manageCollectionsMedia is private by default. Delivery uses authorization and short-lived signed URLs. Configure a persistent disk for production:
INLAY_MEDIA_DISK=s3
INLAY_MEDIA_DIRECTORY=mediaOn MySQL, use the published migration from the current package. Disk names and object paths are intentionally bounded so the composite uniqueness key remains within InnoDB's index limit.
Imports
Install the validation-driven import pipeline:
composer require inlayphp/imports
npm install @inlayphp/imports-reactThe package does not choose a parser, upload disk, queue, or HTTP routes. Define an importer:
Imports and XLSX exports remain separate packages so CSV/import-only projects do not pull PhpSpreadsheet and its extension requirements. When an application needs both workflows, install them together:
composer require inlayphp/imports inlayphp/tables-xlsxfinal class UserImporter extends Importer
{
public function validation(): string
{
return UserRules::class;
}
public function columns(): array
{
return [
ImportColumn::make('name')->requiredMapping(),
ImportColumn::make('email')
->aliases('Email Address', 'E-mail')
->requiredMapping(),
ImportColumn::make('active')
->castUsing(fn (mixed $value) => filter_var($value, FILTER_VALIDATE_BOOL)),
];
}
}ImportValidator::preview() and ImportProcessor::process() reuse the same
validation class as Forms and Resources. Row failures are isolated and include
the stage (cast, transform, resolve, authorization, validation, or
persistence). For large files, queue an application job containing a stable
upload reference—not an authenticated user object or a query builder.
Two-factor authentication
Install the optional plugin:
composer require inlayphp/two-factor-authentication
php artisan vendor:publish --tag=inlay-two-factor-config
php artisan migrateImplement the contract on the same model used by the panel guard:
use Inlay\TwoFactorAuthentication\Concerns\HasTwoFactorAuthentication;
use Inlay\TwoFactorAuthentication\Contracts\TwoFactorAuthenticatable;
final class User extends Authenticatable implements TwoFactorAuthenticatable
{
use HasTwoFactorAuthentication;
protected function casts(): array
{
return [
'two_factor_confirmed_at' => 'datetime',
'two_factor_recovery_codes' => 'array',
];
}
}Register the panel challenge step:
return $panel
->plugin(TwoFactorAuthenticationPlugin::make())
->loginStep(TwoFactorLoginStep::class);The plugin owns encrypted TOTP state, one-use recovery codes, enrollment, and the pending login challenge. Bind a QR renderer at the application edge; the package intentionally does not force a QR dependency.
Do not register the Inlay login step and a Fortify challenge bridge for the same login flow. Existing Fortify models can use the compatibility adapter described in the package README.
Spatie adapters
inlayphp/authorization-spatie synchronizes ability definitions with Spatie
roles and permissions. inlayphp/media-spatie bridges the Inlay media catalog
to Spatie Media Library without making either package mandatory for the core.
Install adapters only after deciding which system owns storage and policy. A bridge should not result in two migrations writing the same columns or two authorization systems making contradictory decisions.
Plugin testing checklist
For every plugin, test:
- package discovery and configuration;
- plugin registration and unique ID;
- guest redirect and authenticated route access;
- ability/policy denial and allowed operation;
- Inertia component and contract props;
- React and Vue renderer tests when both are supported;
- migrations on SQLite and MySQL-compatible schema limits;
- production build and Tailwind source scanning;
- uninstall/disabled behavior—no plugin route should remain enabled by accident.