tomatophp/tomato
TomatoPHP is a modular Laravel admin panel built on Filament, giving you a ready-made dashboard with users, roles and permissions, settings, menus, media manager, and a powerful CRUD generator. Extend it with plugins and modules to ship backend apps fast.
Treat this as a scaffold, not a dependency. The first decision isn't "how do I install it," it's "am I on Laravel 10/11 + PHP 8.1+ + Filament v3, greenfield, internal-only?" If any of those is false, stop — there is no incremental retrofit path, and a Laravel 12 target is a non-starter until upstream ships support.
1. Gate the environment before touching Composer.
php -v # need 8.1+
composer show laravel/framework | grep versions
composer show filament/filament | grep versions
Pin explicitly in composer.json (no ^ on a single-maintainer package) and commit the lockfile:
"require": {
"tomatophp/tomato": "1.3.1",
"filament/filament": "3.x.y",
"laravel/framework": "10.x.y"
}
2. Install into a throwaway branch of a throwaway app. Do the first install somewhere you can rm -rf — a fresh laravel new tomato-spike. You want to see what the installer publishes (config, migrations, assets, a module or app/Tomato directory) without contaminating a real project.
composer require tomatophp/tomato
php artisan tomato:install # or whatever the installer is named
php artisan list | grep -i tomato # discover the actual command surface first
php artisan migrate
Always run php artisan list | grep -i tomato before trusting any README. The command names, generator flags, and module paths drift between releases, and the docs lag the code — the artisan list is ground truth.
3. Where to look first, in order:
config/tomato.php (and any sibling config/tomato-*.php) — this is where the panel path, middleware stack, module registration, and localization/RTL defaults live. Read it end to end; it's the map of the whole system.function across the package to inventory the global function surface. You will collide with it eventually; know what's there now.4. First use case: one boring CRUD, end to end. Pick the smallest real internal resource you have — a Client, a Vendor, a Location. Something with 6–10 fields, one relationship, and no workflow. Generate it, migrate, log in to /admin, and click through create/read/update/delete plus the role screen. That single loop exercises the generator, the Filament panel wiring, the permission system, and the media/settings modules at once, and it's the shortest path to a real hours-saved number against the honest baseline (php artisan make:filament-resource).
Generator-first, runtime-second. The highest-value usage pattern is: use the generator to produce ordinary Laravel/Filament code, commit that code, and do not take a runtime dependency on the helper layer where you can avoid it. That converts a framework bet into a one-time productivity loan. Concretely:
The generate → review → freeze loop.
php artisan tomato:crud Client --fields=... # discover real flags via artisan help
php artisan migrate
# then: review every generated file, add it to normal PR review
# then: never regenerate Client again
Bake that last line into your contributing notes. The generator is a first-draft tool, not a migration system.
Namespace your own code and never re-export helpers. The package ships a large global function surface. Don't add to it. Put your own utilities in classes under App\Support\... and resolve them via the container. This is the single cheapest insurance against a 3am stack trace that goes through third-party helper code you didn't write.
Isolate behind a module boundary. If you must use the runtime modules (menus, settings, media), wrap them:
// app/Support/Settings/SiteSettings.php
final class SiteSettings
{
public function get(string $key, mixed $default = null): mixed
{
return setting($key, $default); // the only place this helper appears
}
}
Now an upstream break is one file to fix, and you can swap the implementation for a plain config() or a settings table read without touching call sites.
Use the roles/permissions UI as the source of truth, but check policies in code. The admin UI is great for defining permissions; don't let it become the only place authorization logic lives. Keep Laravel policies as the enforcement layer so authorization is testable and greppable, with the UI as the editing surface.
Localization/RTL: set it once at the panel level. Tomato's Arabic/English and RTL support is a real differentiator for Gulf/MENA internal tooling. Configure the locale switch in the panel provider rather than sprinkling app()->setLocale() through resources — per-request locale flipping inside Filament pages tends to fight the panel middleware.
Plugin selection is a scope decision, not a shopping spree. Every plugin you enable is another single-maintainer surface in your dependency graph. Enable the three or four you actually need (likely: permissions, settings, media, and one content plugin), and record why each is there. An unexamined plugin list is how a two-week spike becomes an eighteen-month maintenance obligation.
Test the generated code like normal code. Generated resources deserve at minimum a Livewire/Filament smoke test per resource — list page renders, create with valid data, create with invalid data fails validation. This is what catches generator output that quietly doesn't match your schema after a schema change.
Regeneration is destructive and silent. Hand-edit a generated resource, run the generator again, and you lose the edits with no warning and no diff. Ban regeneration in CI and in code review. If a schema change is needed, edit the resource by hand like a grown-up.
Global helper collisions produce unattributable stack traces. With a large global function surface and third-party ownership, name conflicts surface deep in someone else's code during an incident. Symptom: a Call to undefined function or a mysteriously wrong return value with a trace that never touches your files. Mitigation: inventory the helpers early (grep -r "^function " vendor/tomatophp), namespace your own code, and prefer container-resolved classes.
Version pinning is not optional. The last release is April 2024, sitting in the Laravel 10/11 + Filament v3 era. With zero recorded dependents, no downstream population is absorbing and reporting upgrade pain — you will be first, in your own sprint. Pin every transitive constraint, commit the lockfile, and write the tested matrix into the repo README so the next engineer isn't guessing.
Run the one-day upgrade rehearsal before shipping anything real. In the spike repo, attempt a Filament minor bump and a Laravel minor bump separately. Record exactly what breaks. This is the highest signal-per-hour test available on this package, and it's the thing that tells you whether the exit path is "fork and patch" or "fork and rewrite."
Debugging the panel when things 404 or 403:
config/tomato.php and the panel provider's path().php artisan filament:assets.composer why filament/filament.Extension points to prefer. Configuration publishing and Filament's own resource/page extension (custom pages, actions, widgets, table/ form schema callbacks) are stable, documented seams. The package's own module registry and helper API are not — they're internal contracts that can move. Extend through Filament's public API where possible.
Budget the maintenance, not just the spike. MIT means the failure mode is "fork and maintain," not "locked out" — but someone owns that fork and that time is real. Before the second use of this package anywhere, name an owner and write down the exit criteria. A successful spike is not a platform standard; don't let it become one by default.
Never put this in a customer-facing, revenue-critical, or compliance-sensitive path. Internal admin, ops dashboards, back-office CRUD, pilot MVPs with no external users. That's the envelope. Outside it, the generator's speed is irrelevant next to the support-model risk.
How can I help you explore Laravel packages today?