Laravel RBAC Without External Packages
When I built the role system for Laravel Translations v2, I had a constraint: no external RBAC packages. The package already depends on Laravel — adding Spatie Permissions or Bouncer would increase the dependency tree for something that could be built with Laravel's built-in tools.
Here's how I designed it.
The Roles
Four roles, defined as a backed enum:
The enum itself knows what each role can do. No need for a permissions table or a role-permission pivot. The logic is in code, type-safe, and version-controlled — exactly the kind of pattern that PHP 8.4's enums make elegant.
Why Not a Permissions Table?
For a package, a permissions table adds complexity:
- Extra migrations in the host application
- Seeder dependencies
- Potential conflicts with the host app's own RBAC system
- More queries for every authorization check
With an enum, permissions are computed at runtime. No database queries, no cache invalidation, no sync issues.
Policy-Based Authorization
Laravel policies handle the authorization logic:
Notice the translator check: they can only edit translations for languages they're assigned to. This is enforced at the policy level, so it works everywhere — controllers, Blade, API, Artisan commands.
Language Assignment
Roles aren't the only access dimension. Contributors can be restricted to specific languages:
Admins and owners can see everything. Translators and reviewers only see their assigned languages. This means your French translator doesn't see the German translations, reducing noise and preventing accidental edits.

Middleware for Route Protection
A custom middleware gates access to routes based on role:
Applied to routes:
Dual Auth Mode Complexity
The tricky part was supporting two authentication modes. In "contributors" mode, the Contributor model handles auth. In "users" mode, the host app's User model does.
The solution: a TranslationAuth service that abstracts the active guard:
Policies and middleware use this service instead of accessing auth directly. The rest of the codebase doesn't care which auth mode is active.
Takeaway
You don't need a package for RBAC. For most applications — and especially for packages that need to stay lightweight — Laravel's built-in enums, policies, and middleware give you everything you need. Keep it simple, keep it in code, and keep it testable.