tomatophp/tomato-roles
Tomato Roles is a Laravel package for managing roles and permissions with a ready-made admin UI. Create roles, assign permissions to users, and control access across your app without building the CRUD yourself. It fits the TomatoPHP ecosystem and keeps role-based access simple to configure and maint...
Architecture fit. tomatophp/tomato-roles is an ACL layer, not an authorization engine. Its entire reason to exist is the pre-wired role/permission admin surface inside Tomato Admin on Splade (Laravel + Livewire + Blade), reportedly delegating to spatie/laravel-permission. That makes it an internal-tooling fit only: back-office, support, and operations users managing admin-console access. It is not a candidate for customer-facing authorization regardless of how clean the demo looks. If our admin UI is Filament, Nova, Inertia, or a custom SPA, this package delivers zero value — the underlying schema is almost certainly Spatie, which we could adopt directly.
Integration feasibility. Mechanically low-friction: publishable config, migrations, and policies; a HasRoles-style trait on User; plugin registration per Tomato Admin convention; seeders for default roles and guards. A working end-to-end role assignment is a half-day of work in a compatible codebase. The real feasibility question is not "can we install it" but "does deny-by-default hold at both the HTTP and Blade layers," which is a 15-minute spike we should treat as a hard go/no-go gate rather than a formality.
Technical risk. This is the crux of the evaluation, and it is asymmetric in the wrong direction:
| Risk | Evidence | PM consequence |
|---|---|---|
| Ecosystem validation | 2 stars, 0 dependents, no CI signal, no support channel | We are the QA department; budget for reading src/ |
| Single-maintainer concentration | ~21 patch releases, one primary maintainer | Fork-and-vendor is the operating model, not a contingency |
| Stack coupling | v1.1.21 fixed a Splade SelectFilter API change in UserTable |
A minor Splade/Livewire bump can break the admin surface |
| Silent privilege escalation | Authorization defects fail open, not visually | Validation is funded as part of adoption, not follow-up |
| Guard semantics | Non-default admin guards are standard in Splade |
#1 production authz bug in this ecosystem |
| Multi-tenancy | No evidence of team/tenant scoping | Hard stop if our admin is tenant-partitioned |
Note the v1.1.21 fix landed in a filterable UserTable, meaning the shipped blast radius is broader than a narrow role editor — it reaches into user management list screens. That is a scoping fact, not a footnote.
Key questions to resolve before approval.
composer.json wrap spatie/laravel-permission, or reimplement the schema and checks? Wrapping = thin UI shell, acceptable risk. Reimplementing = bespoke authorization core with near-zero external review.permission:cache-reset equivalent, and is it wired into the role-edit flow?UserTable overlap functionality we already own, creating a duplicate user-management surface?Stack fit. Adopt only if we are already committed to Tomato Admin + Splade. The package removes boilerplate CRUD — role editor, permission attach, user assignment — and creates no differentiation. Frame it internally as a time-boxed accelerator for an existing "admin roles and permissions" milestone, not a platform bet. If the stack match is absent, the correct move is spatie/laravel-permission plus a hand-built admin UI at roughly 2–4 engineer-days.
Migration path. Assume a brownfield codebase with ad-hoc is_admin booleans and scattered policy methods. The path is: inventory existing authorization checks → define a canonical permission list as data (config/permissions.php or a seeder) using resource.action dotted names → seed roles per environment → run the new permission model in parallel behind a feature flag for one release cycle → reconcile discrepancies → cut over. Do not publish migrations unless we need to alter the schema; once published, we own it and upgrades become manual diffs. Diff the shipped migrations against any pre-existing ACL tables first, and decide UUID vs bigint up front — retrofitting after data exists is a real migration, not a config tweak.
Compatibility. Pin the exact version matrix — Laravel, Livewire, Splade, and this package as a tested tuple. Never float on ^1.1. Likewise, if the package wraps Spatie, pin Spatie explicitly in our own composer.json so the engine version is not transitively dictated by a low-adoption wrapper. Treat every Splade/Livewire bump as a smoke-test event for the admin list screens.
Sequencing (each step is a gate).
composer.json, database/migrations/*, TomatoRolesServiceProvider.php + models. Answers the wraps-Spatie question and the schema fit. (~2 hours, no commitment.)admin/viewer. Prove deny-by-default at both layers with one test asserting a viewer gets 403 on an edit route and does not see the corresponding @can block. Stop here if it fails.Permissions enum/constants file referenced by middleware, Blade, policies, and tests; Gate::before super-admin bypass returning null; negative-case and cache round-trip tests.Integration pattern to enforce in review. Layer authorization deliberately: route-level coarse gate, explicit abort_unless/can() check in controllers and Livewire actions, and Blade @can as cosmetic only. Every @can must have a matching server-side check behind it or we have shipped a UI that lies about its security posture. Prefer $user->can('permission') over hasRole() so business logic stays decoupled from role names — that is also our swap path later.
Maintenance. Treat this as vendored, not depended-on. Low stars and zero dependents mean upstream fixes arrive on one maintainer's schedule; the active patch line (v1.1.20 → v1.1.21 with a merged external PR) reassures on abandonment risk but says nothing about correctness. Name a fork owner and keep the exit runbook current. Expected upkeep: 2–5 engineer-days per Laravel major, 5–10 per Splade/Livewire major, plus smoke tests on admin list screens with each bump.
Support. None. No Stack Overflow tag, no Discord, no paid tier, no documented support channel. Escape hatches are reading src/ and Spatie's docs. Keep the source open beside the IDE for week one and expect the first integration bug to be diagnosed by reading code, not by asking anyone.
Scaling. Two cost centers. First, the shipped UserTable extends into user management — filterable admin list queries are the expensive ones at scale, so instrument them before the admin user base grows. Second, permission cache behavior: cache invalidation here is a security bug, not a performance bug. A stale cache means a revoked role retains access until TTL expiry. The round-trip test (assign → assert allowed → revoke → assert denied, without process restart) belongs in CI, and a cache reset must be wired into the role mutation flow.
Failure modes and how we detect them.
web-guard user satisfying an admin-guard hasPermissionTo(), or the same role name in another tenant inheriting permissions. Detect via explicit negative tests per guard; this is the top production authz bug in this ecosystem.@can with no matching middleware or check behind it.SelectFilter issue is the template: an upstream API change surfacing as a package bug rather than a clean composer conflict.Ramp-up. Roughly 0.5 day to first working role assignment, 1–2 days to production confidence including the negative-case and cache tests. Add 2–4 days if we build a custom role editor for dimensions the packaged UI does not understand (per-department scoping, row-level rules — expect to fight the fixed UI there, not the trait). Recommend a named fork owner and a short internal runbook covering the three debugging commands (getRoleNames(), getAllPermissions(), Gate::forUser()->allows()) so triage does not depend on one person's memory.
PM recommendation. Approve as an opportunistic internal-tooling dependency conditioned on three gates: (a) the deny-by-default spike passes at HTTP and Blade layers, (b) the wraps-Spatie question resolves affirmatively, and (c) the exact version matrix is pinned and covered by tests. Reject if authorization is customer-facing, security-critical, compliance-driven, multi-tenant, or requires ABAC/SSO/row-level policy. Because domain code continues to ask $user->can('resource.action'), this decision is reversible within a sprint — which is precisely what makes it approvable at all.
How can I help you explore Laravel packages today?