tomatophp/tomato-admin
Tomato Admin is a Laravel admin panel built on Splade that gives you a ready-made dashboard with authentication, roles and permissions, a CRUD generator, menus, notifications, and translation tools, so you can scaffold complete backend interfaces quickly with minimal code.
Install and discover before you build. Add the package to a scratch Laravel app first, then run php artisan list | grep -i tomato and php artisan vendor:publish (interactive, so you can see the tags) before reading a line of source. The published config, migrations, and asset tags tell you more about the package's actual shape than the README does, and they differ between releases.
Minimal path:
composer require tomatophp/tomato-admin on a Laravel version that matches the package's composer.json constraint (check that file — not the docs).php artisan vendor:publish --provider="TomatoPHP\TomatoAdmin\TomatoAdminServiceProvider" (or use the interactive prompt and note the tags it lists).php artisan migrate, then seed an admin user — either the shipped seeder or a quick tinker user with the right role attached.php artisan storage:link if media/avatars are part of the shipped screens./admin and confirm you can see the users table.Where to look first, in this order:
routes/ in the package — the admin route group's prefix, middleware stack, and auth guard assumptions.config/tomato-admin.php after publishing — guard name, user model, menu registration, feature toggles.src/Providers/ — what is bound into the container and what views/Livewire components are registered.tailwind.config.js and CSS entry — the content globs here are the single most likely source of "why is it unstyled" bugs.First use case to build. Pick one small domain model you actually need (e.g. Announcement) and take it end-to-end: migration, model, one admin screen with list/create/edit/delete, one menu entry, one permission gate. Do it by copying the Users resource, not by improvising. If that slice works in a production build (npm run build, served from the compiled assets), the remaining screens are repetition. Budget a day for this, not a week — the week is for when the theme build fights you.
Copy the neighbour, then adapt. Until you've done three screens by hand, don't trust the CRUD generator blindly. Open the shipped Users resource, duplicate the file set into your namespace, rename, and adjust the fields. This gives you working conventions (naming, authorization, redirect behaviour, flash messages) for free, and it's the pattern to write down in an internal "how to add an admin screen" doc on day two — new hires will copy that doc, not the package.
Use the generator as a first draft. Where it works, run the package's scaffolding command into a throwaway directory, diff what it produces against your copied Users template, and merge by hand. Generators here produce plausible-but-incomplete output: fields, but no authorization; routes, but no menu entry. Treat them as a typing shortcut, never as a finished screen, and never run them over an existing resource.
One screen = a checklist. Every admin screen you add should touch the same six places, and it's worth formalising them:
@can / authorization check in the component.Keep validation single-sourced. Don't re-declare rules in the admin form schema and again in an API controller. Put rules in a Form Request or a static rules() on the model/service and reference them from both. When the admin screen and the public form disagree, the admin screen is the one nobody tests.
Thin components, fat services. The Livewire/Filament component should do three things: authorize, call a service, render. Anything with a business rule in it — status transitions, stock math, notification dispatch — lives outside the admin layer. This is what makes the eventual swap to vanilla Filament a re-skin instead of a rewrite, and it's free if you do it from screen one.
Permissions: define and cache deliberately. Create permission names per resource (announcements.view, announcements.edit) rather than per-page, seed them alongside the roles package's data, and reset the permission cache in the same seeder or deploy step (Spatie caches; stale caches produce "403 for the admin" that looks like a middleware bug). Gate the menu entry too, not just the action — silently hiding the link is much better UX than a 403 landing page.
Settings as configuration, not code. For values operators should change (branding, feature toggles, mail recipients), use the sibling settings package and read through a cached accessor (Setting::get('key', $default)). Never put credentials there — settings tables end up in database dumps and support screenshots.
Table performance from day one. Eager-load every relationship a column renders (->with('owner')), keep default pagination modest (15–25 for dense tables), and add search indexes on any column you mark searchable. Admin tables are where N+1 queries go unnoticed for months and then make a 5,000-row screen unusable.
Local workflow that pays for itself. A php artisan admin:demo command (or a seeder) that creates a known admin user, a couple of roles, and 50 rows of demo data per resource. Every developer runs it after a fresh clone. Pair it with a composer dev script that runs Vite in watch mode alongside serve. Without this, every onboarding starts with fifteen minutes of clicking around a blank dashboard.
Testing the admin, minimally but honestly. You won't get full coverage on generated screens, and you shouldn't try. Do three things: an HTTP smoke test that logs in as an admin and asserts GET /admin is 200; a Livewire test on the one or two screens that carry real logic (status transitions, computed totals); and a permission test asserting a non-privileged user gets 403 on each resource route. That trio catches nearly every regression an upgrade will introduce — the visual ones are caught separately, by a screenshot.
Fork-and-pin early. If you're going to modify a shipped view or component (you will), vendor the package into a private repo on day one rather than after the first override. Keep a short UPSTREAM.md listing files touched, and diff against upstream on a scheduled basis rather than during a composer update at 5pm on a Friday.
Unstyled screens are almost always the Tailwind content glob. The theme's tailwind.config.js needs the package's Blade views in content (usually ./vendor/tomatophp/**/*.blade.php plus ./resources/views/vendor/**). Miss it and you get correct HTML with no classes — which reads as "the theme is broken" but is really "PurgeCSS deleted everything it couldn't see." It often works in dev (JIT sees the files) and fails only in the minified build. Run a production build on day one, not day ten.
Published assets are a deploy-pipeline decision, not a code decision. vendor:publish writes theme files into public/. If your CI rebuilds assets but doesn't ship the published ones (or vice versa), staging serves stale CSS with no error anywhere. Pick one owner — either publish during build or commit the published assets — and write it down.
Re-publishing with --force will silently overwrite your view overrides. Before any package bump, check whether you've customised anything under resources/views/vendor/. Prefer config and container bindings over view overrides precisely because they survive upgrades; where you must override a view, copy only that one file and note it in UPSTREAM.md.
Clear caches after every publish or config change. php artisan optimize:clear (or config/route/view individually). Half the "my change didn't apply" reports in admin panels are a cached config or compiled view. Add it to the deploy script, unconditionally.
Mixing Splade's SPA navigation with Livewire components is where the weird bugs live. Splade intercepts link clicks and swaps the page via Vue; Livewire expects to own its component lifecycle. If you see stale state after navigating between admin pages, or a component that works on hard refresh but not on a clicked link, that's this. Find the opt-out (a link attribute or route exclusion in Splade) and apply it liberally on Livewire-heavy pages — don't diagnose it per-page.
A guard mismatch looks like a routing bug. If /admin redirects to login forever, or login succeeds but /admin bounces, verify in order: config/auth.php guard name, the package's expected guard in config/tomato-admin.php, your User model's role trait, and middleware ordering (auth before the panel middleware). Test the login POST, not just the authenticated dashboard render — a guard mismatch passes every test that uses actingAs.
403s after seeding are usually the permission cache. The roles package caches; a seeder that adds permissions doesn't invalidate it. Reset it in the seeder itself and in the deploy step, so the local and production behaviours match.
Migration timestamps can collide. Published migrations run once and are yours. Never edit them after they've run in shared environments; add new migrations instead. If a package migration and one of yours share a timestamp, Laravel's ordering gets non-deterministic and you get intermittent FK failures on fresh installs.
Don't edit vendor/. If you need a behaviour change, it's a config value, a view override, a container binding, or a fork — in that order of preference. Direct vendor edits vanish on the next composer install and nobody will remember they existed.
Pin the ecosystem, not just the package. filament/filament and splade versions move independently of this package and both can break it silently — a Filament major bump degrades styles without throwing; a Splade major can leave you with a blank page and no stack trace. Commit composer.lock, and treat any change to those two as a deliberate, tested upgrade with a visual check, not a routine composer update.
Debugging a dashboard that won't render, in order of likelihood: (1) caches not cleared; (2) Tailwind content globs missing the package's views, so it renders unstyled; (3) published assets missing from the served path; (4) the authenticated user isn't on the guard the package expects; (5) the service provider isn't registered in bootstrap/providers.php; (6) a Filament/Splade version mismatch against what the package's views were written for.
Onboarding tax is real. Your team needs two mental models at once — Livewire/Alpine (server-driven DOM morphing) and Splade/Vue (client-side SPA routing). A mid-level Laravel developer is productive in one to two weeks. Don't hand this to a React-native engineer without budgeting for the context switch; the day-to-day work is mostly Blade and PHP, but the debugging requires knowing which of the two front-ends is misbehaving.
Budget maintenance. Roughly 10–15% of one engineer, ongoing, mostly reacting to Filament and Splade majors plus the occasional Laravel bump. If that number isn't acceptable, the honest alternative is vanilla Filament — you'd write the same screens, you'd just write the theme and auth scaffolding yourself.
How can I help you explore Laravel packages today?