Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tomato Roles Laravel Package

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...

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the stack before anything else. tomatophp/tomato-roles is an ACL layer for Tomato Admin on Splade (Laravel + Livewire + Blade). It is not a general-purpose permission package — the value it adds is the pre-wired admin UI (role editor, permission attach, user-role assignment) on top of what is almost certainly spatie/laravel-permission. If you're on Filament, Nova, Inertia, or a custom SPA, you get nothing but transitively-pulled dependencies.

Read these three files first — they determine your entire risk profile:

File What it tells you
composer.json Whether it wraps spatie/laravel-permission or rolls its own engine; which Laravel / Livewire / Splade versions it pins
database/migrations/* Table names, key types (UUID vs bigint), guard column, team/tenant scoping — diff against your users table
src/TomatoRolesServiceProvider.php + models How the panel plugin registers, where the HasRoles-style trait lives, what config keys exist

That first row is the decision point. Wrapping Spatie = thin UI shell, low risk. Reimplementing = bespoke authorization core with zero external review. Two minutes of reading changes how you plan the next two weeks.

Install on a throwaway branch:

composer require tomatophp/tomato-roles
php artisan vendor:publish --tag=tomato-roles-config
php artisan vendor:publish --tag=tomato-roles-migrations   # only if you need to change schema
php artisan migrate

Then register the panel plugin per the Tomato Admin convention and add the package's trait to your User model. Publish migrations only when you must — once published, you own that schema and every future upgrade becomes a manual diff.

Prove deny-by-default in 15 minutes. This is the go/no-go gate:

// database/seeders/RoleSeeder.php
$admin  = Role::create(['name' => 'admin']);
$admin->givePermissionTo(['users.view', 'users.edit']);

$viewer = Role::create(['name' => 'viewer']);
$viewer->givePermissionTo('users.view');
// routes/web.php
Route::middleware(['auth', 'role:admin'])->group(function () {
    Route::get('/admin/users', UserTable::class);
});
@can('users.edit')
    <x-tomato-button wire:click="edit">Edit</x-tomato-button>
@endcan

Then write one test asserting a viewer gets 403 on the edit route and doesn't see the button. If deny-by-default holds at both the HTTP and Blade layers, you have the core loop. If it doesn't, stop — you'd be building on a broken foundation.

Your day-one loop is: seed roles → open the Tomato Admin user table → assign roles through the UI → confirm gated routes and menu items behave. That loop is roughly 80% of what you'll actually touch.

Implementation Patterns

Treat role/permission definitions as data, not scattered code. Dotted resource.action strings in a seeder (or a config/permissions.php map) make every environment reproducible. That list is a public contract — renaming users.view breaks every middleware string, Blade directive, and test in the repo simultaneously.

Layer authorization deliberately, in this order:

// 1. Route-level — coarse gate
Route::middleware(['auth', 'permission:users.view'])->group(...);

// 2. Livewire / Controller — check before acting
abort_unless(auth()->user()->can('users.edit'), 403);

// 3. Blade — cosmetic only
@can('users.edit') ... @endcan

Blade @can hides UI; it protects nothing. Every @can you ship needs a matching middleware or server-side check behind it, or you've built an interface that lies about its security posture.

Prefer $user->can() over $user->hasRole(). Role names are volatile, permissions are the durable contract. A UserPolicy or Gate::define calling hasPermissionTo() keeps your domain logic decoupled from the ACL backend — which is precisely your swap path later.

Centralize the permission list in one class (an enum or constants file) referenced by middleware, Blade, policies, and tests. When you migrate to raw Spatie or a policy layer, that file is the only thing that changes.

Super-admin bypass via Gate::before, never scattered if ($user->isAdmin()):

Gate::before(fn ($user) => $user->hasRole('super-admin') ? true : null);

Return null — never false — so normal checks still run for everyone else.

Assign roles in application flows, not just the admin UI. Plan upgrades and migrations are the common case:

User::where('plan', 'pro')->each(fn ($u) => $u->assignRole('pro'));

Run parallel checks behind a feature flag for one release cycle and reconcile any ad-hoc is_admin booleans or policy methods against the new model before cutting over.

Test the negative cases and the cache round-trip — these are the ones that matter:

it('denies viewer from editing users', function () {
    $user = User::factory()->create()->assignRole('viewer');
    $this->actingAs($user)->get('/admin/users/1/edit')->assertForbidden();
});

it('clears cached permissions on role revocation', function () {
    $user->assignRole('admin');
    expect($user->can('users.edit'))->toBeTrue();
    $user->removeRole('admin');
    expect($user->fresh()->can('users.edit'))->toBeFalse();
});

Custom permission matrices (per-department scoping, row-level rules) are the part you'll fight. The trait usually bends fine; the built-in role editor UI is fixed and doesn't understand custom dimensions — budget for building your own admin surface there.

Gotchas and Tips

Pin exact versions — never float on ^1.1. With a single-maintainer package on a Splade/Livewire stack, a minor bump shows up as a Livewire component failure rather than a clean composer conflict. The v1.1.21 fix — removing an unsupported applyQuery parameter from a SelectFilter in UserTable — is exactly that category: a Splade filter-API change surfacing as a "package bug." Lock Laravel, Livewire, Splade, and this package as a tested matrix in composer.json.

Resolve "wraps Spatie or reimplements it" before committing. If it delegates, you inherit a battle-tested engine and this package is a UI shell — good risk profile. If it reimplements the schema and checks, you own a bespoke authorization core with essentially no external review. If it does wrap Spatie, pin Spatie explicitly in your own composer.json so your engine version isn't transitively dictated.

Cache invalidation is a security bug, not a perf bug. Test assign → assert allowed → revoke → assert denied without restarting the app. If the package doesn't expose a permission:cache-reset command (Spatie's name), add one and wire it into your role-edit flow. A stale cache means a fired employee keeps access until the TTL expires.

Guard semantics are the most common silent killer. Splade admins frequently use a non-default admin guard, and cross-guard permission leakage is the #1 production authorization bug in this ecosystem. Test explicitly:

  • A web-guard user cannot satisfy an admin-guard hasPermissionTo().
  • The same role name in a different tenant/team does not inherit permissions.

If your app is multi-tenant and this package is global-scope only, that's a hard stop unless you wrap it yourself.

Diff the published migrations against any pre-existing ACL tables before migrating. Choose UUID vs bigint keys up front — retrofitting after data exists is a real migration, not a config tweak.

UserTable is bigger than "role editor." The v1.1.21 fix lives in a filterable user list, so the shipped surface extends into user management. Budget smoke tests on admin list screens for every Splade/Livewire bump — and expect those queries to be the expensive ones at scale.

Debug failing checks in this order:

php artisan tinker
>>> $u = User::find(1);
>>> $u->getRoleNames();                        // is the role attached?
>>> $u->getAllPermissions();                   // does the role carry it?
>>> Gate::forUser($u)->allows('users.edit');   // what does the gate actually see?
  • getAllPermissions() includes it but allows() is false → guard mismatch.
  • allows() is true but @can hides it → directive/panel-context issue (usually the wrong guard in Blade).
  • Clearing the cache fixes it → stale cache, add a reset to your role-mutation flow.

Treat it as vendored, not depended-on. Two stars, zero dependents, no CI signal, ~21 patch releases on one maintainer. Keep a named fork owner and pre-write the exit runbook to raw spatie/laravel-permission or a policy layer. The active patch line (v1.1.20 → v1.1.21, external PR merged) reassures on abandonment, not correctness — and a bug in the permission-check path is silent privilege escalation, not a visual glitch.

There is no support channel. No Stack Overflow tag, no Discord, no paid tier. Your escape hatches are reading src/ and reading Spatie's docs. Keep the source open beside your IDE for week one.

Don't ship the ACL cutover inside a feature launch. Give it its own release so rollback is clean. Rough budget: 0.5 day to first working role assignment, 1–2 days to production confidence including tests, then 2–5 engineer-days per Laravel major and 5–10 per Splade/Livewire major for upkeep.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk