DEV Community

Sebastian Cabarcas
Sebastian Cabarcas

Posted on

Redis-backed permissions for high-volume Laravel apps: v4.0.0-beta.1

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
Enter fullscreen mode Exit fullscreen mode

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],
Enter fullscreen mode Exit fullscreen mode

Both types are warmed, both are covered by the Gate::before super-admin callback.

Blade directive guard override

@role('admin', 'api')
    ...
@endrole
Enter fullscreen mode Exit fullscreen mode

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 — Redis EXEC returning 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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)