User guide
Testing and deployment
Inlay has two contracts to protect: the PHP contract that builds and secures a screen, and the React/Vue renderer that presents it. Test both layers.
PHP tests
Run the package suite from the monorepo:
composer lint
vendor/bin/pestFor an application, prefer feature tests that authenticate a real model and exercise the route:
it('shows the protected user Resource', function (): void {
$this->actingAs(User::factory()->admin()->create())
->get('/admin/users')
->assertOk()
->assertInertia(fn ($page) => $page
->component('users/index')
->has('table')
->where('inlayPanel.id', 'admin'));
});Cover these boundaries for every Resource or plugin:
- guest redirect to panel login;
- authenticated access;
- policy denial for each operation;
- tenant or ownership scope;
- invalid validation data;
- successful create/update/delete;
- action confirmation and lifecycle hooks;
- expected Inertia contract and component name;
- migration on the database engines used in deployment.
Contract tests
Forms:
FormTester::make($form)
->assertFormFieldExists('email')
->assertFormFieldRequired('email')
->assertFormFieldDoesNotExist('internal_token');Tables:
TableTester::make($table)
->assertTableColumnExists('email')
->assertTableFilterExists('status')
->assertTableActionExists('edit')
->assertCanSeeTableRecords($visibleUsers)
->assertCanNotSeeTableRecords($hiddenUsers);These testers inspect the same serialized payload consumed by React and Vue. They are not a replacement for HTTP authorization tests.
Frontend tests
Run the adapter checks for the renderer you ship:
npm test
npm run typecheck
npm run buildIn a monorepo, the package-specific commands are available through pnpm:
pnpm test:frontend
pnpm typecheck
pnpm buildAdd browser tests for the important user paths:
- login and logout;
- open dashboard and navigation;
- search and filter a table;
- open create/edit form and display validation errors;
- confirm a destructive action;
- switch light/dark mode;
- open the mobile navigation drawer;
- use keyboard focus and submit controls.
The frontend should never be the only test of authorization. A hidden button is not a security boundary.
Installation checks
Every clean Laravel application should pass:
php artisan inlay:doctor
npm run build
php artisan inlay:doctor --productioninlay:doctor --production catches the most common deployment failure: the
JavaScript loads but Tailwind did not scan the installed @inlayphp packages,
leaving forms and tables unstyled.
CI checklist
A package or application workflow should run:
Composer install with the committed lock
PHP lint and Pest
Frontend install with the committed lock
Frontend tests and typecheck
Production frontend build
Laravel integration tests
Migration testsRun the build before typecheck/test when a clean checkout needs generated frontend declaration files. Keep PHP 8.3 as the lowest supported runtime and test the highest supported PHP version as well.
Standalone deployment
Commit both lockfiles and build inside the deployment repository:
composer install --no-interaction --prefer-dist --optimize-autoloader
npm ci
npm run build
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan inlay:doctor --productionUse pnpm install --frozen-lockfile or the equivalent command when that is the
committed package manager.
Do not use Composer path repositories or npm link: dependencies in a clean
deployment repository. Publish Composer packages to Packagist (or configure a
private Composer repository) and publish renderer packages to npm. A standalone
Laravel Cloud checkout cannot see sibling monorepo directories.
Laravel Cloud and object storage
Laravel Cloud's local filesystem should not be treated as durable media storage. Configure an attached S3-compatible disk:
FILESYSTEM_DISK=s3
INLAY_MEDIA_DISK=s3Install the adapter required by the disk:
composer require league/flysystem-aws-s3-v3Run media migrations before registering the Media Manager. If a migration was
partially applied, repair the database state deliberately; do not hide a
duplicate table by blindly changing every migration to createIfNotExists().
The current media migration guards the known folder-table recovery case and
uses MySQL-safe disk/path lengths.
Environment and security
- never commit
.env, passwords, or package tokens; - use a real password reset or deployment secret for the first administrator;
- enable HTTPS and secure cookies;
- configure queue workers for imports, transformations, and queued exports;
- set storage lifecycle/retention for trashed media;
- keep policy checks on protected routes and actions;
- rate-limit login, password changes, uploads, and expensive exports;
- clear application caches after changing panel providers or themes.
Debugging a blank or unstyled page
Use this order:
php artisan route:list | grep admin— confirm the route exists;- browser console — find a page resolver or JavaScript exception;
- Inertia response — confirm
inlayPanel,form,table, orinlayWidgetsexists for the page; public/build/manifest.json— confirm the entrypoint exists;- compiled CSS — confirm an Inlay class and
--inlay-*variable is present; php artisan optimize:clear— remove stale config/view/routes;php artisan inlay:doctor --production— repeat the automated check.
If /admin/users has a table in the response but the page is blank, the
frontend page resolver likely did not map users/index, or the page was wrapped
in a second starter-kit layout. If the page is unstyled, fix Tailwind source
discovery before changing component markup.