shanerbaner82/panel-roles
Panel Roles is a Laravel package for managing roles and permissions inside Filament panels. Define roles, assign them to users, and restrict access to panel resources and pages with an easy-to-configure setup that keeps authorization consistent across your admin panels.
shanerbaner82/panel-roles is a small Filament panel plugin that handles the two role chores every admin panel needs: giving brand-new users a default role, and keeping certain roles out of the panel entirely.
Install it and register the plugin inside your panel provider:
composer require shanerbaner82/panel-roles
use Shanerbaner82\PanelRoles\PanelRoles;
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->plugin(PanelRoles::make()
->roleToAssign('developer')
->restrictedRoles(['admin', 'developer']),
);
}
}
As of v0.0.3 both roleToAssign() and restrictedRoles() are mandatory. Earlier versions worked without them; a bare PanelRoles::make() will no longer behave as before, so treat this as a breaking change if you are upgrading.
First things to check, in order:
spatie/laravel-permission — run your seeder before touching the panel).restrictedRoles does not contain a role you need in order to administer the panel. Locking yourself out on a fresh install is the classic first-run failure.The first real use case: a /admin panel where self-registered users get developer automatically, while admin and developer roles are the only ones allowed into the panel.
Keep the config in the panel provider, not in config files. The plugin is fluent and panel-scoped, which is its main advantage over a global config: two panels can enforce different rules.
// AdminPanelProvider — staff only
->plugin(PanelRoles::make()
->roleToAssign('staff')
->restrictedRoles(['admin', 'staff', 'super-admin']),
)
// CustomerPanelProvider — customers only
->plugin(PanelRoles::make()
->roleToAssign('customer')
->restrictedRoles(['customer']),
)
Drive role names from constants or config rather than raw strings. Role names appear in seeders, policies, tests, and this plugin. A single Roles class or config/roles.php prevents the slow drift where the panel provider says 'super-admin' and the seeder creates 'superadmin'.
Seed roles idempotently before the panel is ever hit. Standard pattern:
foreach (['admin', 'developer', 'staff'] as $role) {
Role::findOrCreate($role, 'web');
}
Run this in a seeder wired into deployment, not just DatabaseSeeder for local dev — otherwise production's first registration silently fails to assign a role.
Pair it with canAccessPanel(). The plugin's restriction is one gate; your User::canAccessPanel() is another. Keep the logic consistent and lean on the plugin for the "is this role in the allow-list" part rather than duplicating a hardcoded array. If you need an exception (e.g. a suspended user who is also an admin), do it in canAccessPanel() and let the plugin handle the coarse role check.
Test it with a role-aware factory state. In Pest/PHPUnit:
$user = User::factory()->create();
$user->assignRole('admin');
$this->actingAs($user)
->get('/admin')
->assertOk();
$outsider = User::factory()->create();
$outsider->assignRole('customer');
$this->actingAs($outsider)
->get('/admin')
->assertForbidden();
Add one test asserting a freshly registered user receives roleToAssign — that is the behaviour most likely to regress between releases.
Integration tip: the assignment fires at registration time, so any custom registration flow (Fortify, Breeze, a Filament registration page, or an invite-based flow) should be verified to go through the path the plugin hooks. If you create users via an admin UI or an import, check whether the role is applied or handle it explicitly with assignRole().
restrictedRoles() may either throw or, worse, leave the panel unrestricted. Pin the version in composer.json and read the release notes before bumping; pre-1.0 packages at 0.0.x make no stability promises.restrictedRoles keeps their session until it expires or they log out. Force a re-check (or php artisan sessions:clear / cache clear) if you are tightening access urgently.'Admin' ≠ 'admin', and a typo produces a panel that silently allows everyone or nobody. Log the resolved roles at boot in local dev if you are unsure.web and admin guards), confirm the roles exist on the guard the panel uses. spatie/laravel-permission caches permissions; after changing role definitions run php artisan permission:cache-reset.restrictedRoles broad, verify access, then tighten. Keep a console tinker route to re-add your own role if you do lock yourself out.$user->getRoleNames() in tinker tells you whether the problem is assignment (plugin config) or access (panel/policy). Don't start by editing canAccessPanel().PanelRoles or decorating its internals — you'll be rewriting on every patch release. If you need conditional logic (assign role X only for invited users), keep it in your registration flow and leave the plugin to the static default.make() with no arguments and relying on defaults is exactly what v0.0.3 removed. Treat the explicit calls as documentation of intent and don't factor them out into a helper that "just returns a configured instance" — being able to read the roles straight from the panel provider is worth the repetition.How can I help you explore Laravel packages today?