geniusts/roles
A Laravel package for managing roles and permissions with a simple API. Assign roles to users, define permissions, and control access across your application using lightweight, flexible tools that integrate cleanly with Laravel's authorization system.
Install and scaffold — pull the package in and publish its assets:
composer require geniusts/roles
php artisan vendor:publish --provider="GeniusTS\Roles\RolesServiceProvider"
php artisan migrate
Publishing gives you the roles, permissions, role_user, and permission_role tables plus a config/roles.php you should skim before migrating in production (table names, model bindings, middleware behavior).
Wire the trait into User — this is the only required code change:
use GeniusTS\Roles\Traits\HasRoleAndPermission;
class User extends Authenticatable
{
use HasRoleAndPermission;
}
Seed your baseline — the package is seed-driven, not UI-driven. Create roles with a slug (the stable identifier you'll reference in code) and a level (used for hierarchy), then attach permissions:
$admin = Role::create(['name' => 'Admin', 'slug' => 'admin', 'level' => 10]);
$admin->attachPermission('edit.posts');
$admin->attachPermission('delete.posts');
$member = Role::create(['name' => 'Member', 'slug' => 'member', 'level' => 1]);
First use case: gate a route, render a button. Check the role in a route group and in Blade:
Route::group(['middleware' => 'role:admin'], function () {
Route::get('/admin', 'AdminController@index');
});
@role('admin')
<a href="/admin">Admin</a>
@endrole
The middleware aliases role and permission are registered by the service provider, so no manual kernel edits are needed on 6.x/7.x.
Where to look first: the published config (defaults and model overrides), then GeniusTS\Roles\Traits\HasRoleAndPermission — that trait is the whole API surface and reading it will save you hours of guessing.
Role assignment at registration. Attach the default role in your Registered listener or create() flow so no user is ever role-less:
$user = User::create([...]);
$user->attachRole('member'); // slug or Role instance
Hierarchy via level. This is what distinguishes geniusts/roles from flat permission packages. A higher-level user is treated as holding the permissions of lower levels, so $user->level() and $user->can(...) can be used together without explicitly granting every permission up the chain:
if ($user->level() >= 10) { /* admin-only */ }
Permission checks in policies and gates. Because the trait plugs into Gate, prefer $user->can('edit.posts') over checking role slugs in controllers. Reserve hasRole/isRole for coarse UI branching and route grouping; use permissions for anything that could change as the app grows.
Admin management screens. A CRUD page for roles is the standard workflow:
$role->attachPermission($request->permissions); // array of slugs/IDs
$role->detachPermission('delete.posts');
$user->syncRoles($request->roles); // if you need full replace
Wrap role mutations in a transaction — the pivots are written separately and a partial failure leaves dangling relationships if the parent role save fails.
Middleware layering. Register both aliases in route groups and stack them when needed:
Route::middleware(['auth', 'role:admin', 'permission:edit.posts'])->group(...);
Blade composition. Use @role / @permission for single checks and fall back to @can for OR-logic:
@permission('edit.posts')
@include('posts._edit-button')
@endpermission
Testing. Use attachRole in factories/setups and assert with $this->actingAs($user)->get(...)->assertStatus(200). This is faster and more readable than mocking the trait; the package works fine against an in-memory SQLite database as long as you run the package migrations in RefreshDatabase.
php artisan cache:clear is the blunt fallback). Do this in deploy scripts after seeding.level inherits downward, not the reverse. A level => 0 role can't see anything a level => 1 role sees. Set levels deliberately during seeding — changing them later silently changes who can do what.name is for display, slug is for lookup. Enforce uniqueness on slug in your seeds (firstOrCreate) so re-running db:seed doesn't create duplicates; the pivots will otherwise multiply.attachPermission/attachRole will throw. Guard seeds with firstOrCreate and syncWithoutDetaching-style checks where available.$user->hasRole(...) and $user->can(...) trigger relationship queries. On a list of 50 users this becomes a visible N+1; eager load with ->with('roles', 'permissions') on any collection you iterate.Gate and auth internals changed enough that the integration will be flaky. Treat this as a legacy-friendly package, not a forward-looking one.spatie/laravel-permission, which has active support and a larger community; geniusts/roles wins primarily if you want the built-in level hierarchy or an existing codebase already depends on it.role/permission elsewhere, the package's registration silently clobbers or is clobbered depending on provider order. Confirm app/Http/Kernel.php $routeMiddleware after install.$role->delete() orphans role_user rows. Delete with $role->users()->detach() first, or add cascading FKs in a follow-up migration.Role and Permission models. You can swap them via the config to add columns (e.g., description) or override findBySlug. That's the cleanest place to add audit fields without forking.@role is a structural check only — don't pass user input into it, since the slug is looked up directly. Validate roles/permissions server-side in controllers/policies regardless of what the view rendered.How can I help you explore Laravel packages today?