I just shipped v4.0.0-beta.1 of laravel-permissions-redis, and I want to share both what's in it and the specific problem it exists to solve.
The problem
Spatie's laravel-permission is the de-facto permissions package in the Laravel ecosystem and it's great. But it was designed for the common case: users have roles, roles have permissions, you check them a handful of times per request, you hit the DB, everyone's happy.
What happens when you scale that model?
- An admin panel rendering 200+ ACL-gated widgets per page
- An API gateway fanning out to 30 microservices, each check needing authorization context
- A reporting dashboard pulling user-filtered data with permission checks on every field
At that point, each permission check is a DB roundtrip (even if cached at query level), and your p99 latency is now dominated by authorization.
What this package does
Moves the entire read path to Redis.
- Roles and permissions are denormalized into Redis SETs:
user:{id}:permissions,user:{id}:roles - Permission checks become
SISMEMBER— ~0.1ms vs a DB roundtrip of 5-20ms - Writes (assign/revoke) update the DB, fire events, and rewarm the Redis cache
- Cache invalidation is event-driven and automatic
The tradeoff: Redis dependency, cache warming overhead at user login, slightly more complex write path.
What's new in v4.0.0-beta.1
Permission group metadata
Previously PermissionDTO::group was always null because Redis didn't store group data. Now there's a Redis hash (permission_groups) that maps {guard}|{name} → group. getAllPermissions() returns properly enriched DTOs.
Role-level permission checks
Role::hasPermission('posts.create') — direct SISMEMBER on the role's permission set. Useful when you want to know "does the admin role have X?" without loading a user.
Queue-backed cache warming
Warm commands now accept --queue:
php artisan permissions-redis:warm --queue=default
php artisan permissions-redis:warm-user 42 --queue=default
Dispatches WarmAllCacheJob / WarmUserCacheJob instead of running sync. Useful when warming millions of users.
Multi-user-models
Set user_model as an array in config:
'user_model' => [App\Models\User::class, App\Models\Admin::class],
Both types are warmed, both are covered by the Gate::before super-admin callback.
Blade directive guard override
@role('admin', 'api')
...
@endrole
Second argument is the guard name. All six directives (@role, @hasanyrole, @hasallroles, @permission, @hasanypermission, @hasallpermissions) accept it.
UUID/ULID role IDs
If your Role model uses non-integer primary keys, v4 handles it. PermissionRepositoryInterface now types role IDs as int|string.
Defensive additions
- LRU eviction in the in-memory resolver cache — prevents unbounded memory growth in long-running workers (queue workers, Octane). Default limit: 1000 users.
- Warm cooldown — if Redis cache creation keeps failing, the resolver stops hammering the DB with warm attempts (default: 1 second cooldown per user).
-
TransactionFailedException— RedisEXECreturning null/false now throws observable exceptions instead of silently dropping writes.
Breaking change
PermissionRepositoryInterface gained three methods for permission group metadata:
public function setPermissionGroups(array $groups): void;
public function getPermissionGroups(array $encodedNames): array;
public function deletePermissionGroup(string $encodedName): void;
If you have a custom implementation of the interface (tenant-aware or otherwise), you must implement these. If you only use the built-in RedisPermissionRepository, no code changes — just run php artisan permissions-redis:warm --fresh after upgrade to populate the new hash.
Install the beta
composer require scabarcas/laravel-permissions-redis:^4.0@beta
php artisan vendor:publish --tag=permissions-redis-migrations
php artisan migrate
php artisan permissions-redis:warm --fresh
What I'm looking for
1-2 week beta window before I cut rc.1. Feedback on:
- API ergonomics for the new methods
- Clarity of the upgrade guide
- Anything that breaks in your specific setup (tenancy, custom guards, UUID users)
Release notes · GitHub · Packagist
If you try it, let me know how it goes.
Top comments (0)