Download the PHP package scabarcas/laravel-permissions-redis without Composer

On this page you can find all versions of the php package scabarcas/laravel-permissions-redis. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-permissions-redis

Laravel Permissions Redis

A Redis-backed roles and permissions package for Laravel with a Spatie-compatible API. It keeps each user's resolved permissions in Redis and invalidates the cache surgically (one user at a time) instead of flushing everything on every change.

Inspired by spatie/laravel-permission, the de facto standard for roles and permissions in Laravel. This package keeps its familiar API (hasRole, hasPermissionTo, assignRole, Blade directives, middleware) but changes the storage layer: Spatie caches the permission registry yet still lazy-loads each user's role and permission relations from the database per request (about 4 queries), while this package keeps the resolved per-user set in Redis (1 query). The goal is to take authorization load off the database and avoid full cache-flush stampedes on permission changes, not to be dramatically faster. For most apps, Spatie with its cache is simpler and enough; this package earns its place when the database is your bottleneck or permissions change often at scale.

CI codecov Latest Version on Packagist Total Downloads PHP 8.3+ Laravel 11|12|13 PHPStan Level Max


Table of Contents


Requirements

Runtime

Dependency Version Purpose
PHP ^8.3 CI runs on 8.3, 8.4 and 8.5
Laravel Framework ^11.0 \| ^12.0 \| ^13.0 Host application
Redis extension phpredis or predis Redis connectivity

Laravel 11 note: the composer constraint allows Laravel 11, but CI only exercises Laravel 12 and 13 (the Pest 4 test harness requires testbench 10+, which starts at Laravel 12). Laravel 11 reached end of life in March 2026, so support is best-effort on the 4.x line and the constraint will be dropped in v5.

PHP Extensions

The redis extension (phpredis) is recommended in production (pecl install redis). If you can't install it, predis/predis works as a userland alternative: set 'client' => 'predis' in config/database.php under the Redis section.

Laravel Components Used

Package Version Role in the package
illuminate/support ^11.0 \| ^12.0 \| ^13.0 Service provider, collections, helpers
illuminate/database ^11.0 \| ^12.0 \| ^13.0 Eloquent models, migrations, query builder
illuminate/redis ^11.0 \| ^12.0 \| ^13.0 Redis connection manager
illuminate/events ^11.0 \| ^12.0 \| ^13.0 Event dispatching and cache invalidation
illuminate/auth ^11.0 \| ^12.0 \| ^13.0 Gate integration, login event listener

Development Dependencies

Package Version Purpose
pestphp/pest ^4.4 Testing framework
phpstan/phpstan ^2.1 Static analysis
larastan/larastan ^3 Laravel-specific static analysis rules
laravel/pint ^1.29 Code style formatting (PSR-12)
orchestra/testbench ^10.11 \| ^11.0 Laravel package testing harness

Infrastructure

Service Version Notes
Redis Server 6.0+ Required. Uses SET data structures and MULTI/EXEC transactions
Database MySQL 8.0+ / PostgreSQL 13+ / SQLite 3.35+ Any database supported by Laravel migrations

Architecture

Note: The diagrams below use Mermaid. If they appear as code blocks, view them rendered on GitHub.

System Context

Container Diagram

Resolution Flow

Cache Invalidation Flow

Redis Key Structure


Installation

Requirements

Step 1: Install via Composer

The service provider is auto-discovered. No manual registration needed.

Step 2: Publish Assets

Publish config and migrations together:

Or publish individually by tag:

Step 3: Run Migrations

Creates 5 tables: permissions, roles, model_has_permissions, model_has_roles, role_has_permissions.

Step 4: Configure Redis

Ensure your config/database.php has a working Redis connection. The package uses 'default' by default:

Step 5: Warm the Cache

This loads all existing permissions and roles into Redis. Run this after initial setup or after direct database modifications.


Configuration

All options live in config/permissions-redis.php:

Option Env Variable Default Description
redis_connection PERMISSIONS_REDIS_CONNECTION 'default' Redis connection from config/database.php
prefix PERMISSIONS_REDIS_PREFIX 'auth:' Prefix for all Redis keys
ttl PERMISSIONS_REDIS_TTL 86400 Cache TTL in seconds (24h)
user_model PERMISSIONS_REDIS_USER_MODEL App\Models\User Your User model (accepts a string or array of FQCNs)
log_channel PERMISSIONS_REDIS_LOG_CHANNEL null Log channel (null = default)
register_gate (none) true Enable Gate::before integration
register_middleware (none) true Register middleware aliases
warm_on_login (none) true Auto-warm cache on user login
super_admin_role PERMISSIONS_REDIS_SUPER_ADMIN_ROLE null Role that bypasses all checks
wildcard_permissions PERMISSIONS_REDIS_WILDCARD false Enable fnmatch() wildcard patterns
register_blade_directives (none) true Register Blade directives
resolver_cache_limit PERMISSIONS_REDIS_RESOLVER_LIMIT 1000 Max users held in the in-memory resolver cache before LRU eviction
resolver_warm_cooldown PERMISSIONS_REDIS_WARM_COOLDOWN 1.0 Seconds before a failed cache-miss warm is retried for the same user
queue.connection PERMISSIONS_REDIS_QUEUE_CONNECTION null Queue connection used by WarmUserCacheJob (null = default)
queue.name PERMISSIONS_REDIS_QUEUE_NAME 'default' Queue name for warming jobs
seed (none) (see config) Roles and permissions to seed via CLI
tables (none) (see config) Custom table names

Usage Guide

Setting Up the User Model

Add the HasRedisPermissions trait to your User model:

Multiple User Models

If your application authorizes more than one Eloquent model (e.g., User for the web app and Admin for the back office), configure an array of FQCNs:

AuthorizationCacheManager iterates all configured types when resolving role/permission assignments, and the Gate::before callback accepts all of them. Each model still needs the HasRedisPermissions trait.

Creating Roles and Permissions

Use the findOrCreate static method on both models:

Assigning Roles and Permissions

All assignment methods accept: string, int (ID), BackedEnum, array, or Collection.

Checking Roles and Permissions

Query Scopes

Scopes run SQL, not Redis. scopeRole and scopePermission issue database queries against the pivot tables and do not consult the Redis cache. Use them for reporting, admin dashboards, or any context where you need the full user list, not in hot authorization paths (hasPermissionTo, middleware, Blade). For a throughput-sensitive "which users have role X" lookup, call $cacheManager->getUserIdsAffectedByPermission($name) or the equivalent role-cache method, both of which read from Redis.

Middleware

The package registers three middleware aliases automatically:

permission: Require permissions

role: Require roles

role_or_permission: Require role OR permission

Operators: | = OR (any), & = AND (all).

Blade Directives

Every directive also has an unless variant (@unlessrole, @unlesspermission, @unlesshasanyrole, ...) that renders when the check fails. They close with the regular @endrole / @endpermission tags, not @endunlessrole.

Guard Override

All six directives accept an optional second argument to scope the check to a specific guard (defaults to the current authentication driver):

This is especially useful in SPA or API-token contexts where the active web session differs from the guard you want to authorize against.

Gate Integration

When register_gate is enabled (default), Laravel's Gate resolves permissions through Redis:

Wildcard Permissions

Enable in config or .env:

Then assign wildcard permissions:

Uses PHP's fnmatch(), which supports *, ?, and [...] patterns.

Super Admin

Set a super admin role in config or .env:

Cache Management

Artisan Commands

The package also registers a section in php artisan about with the installed version, Redis connection, key prefix, TTL, and whether tenancy, wildcards, and the super admin role are enabled.

Both warm commands accept --queue to push the operation onto the queue instead of running it synchronously. This is the recommended approach for large user tables or post-deploy rewarms:

Under the hood this dispatches WarmUserCacheJob per user; the job is idempotent and safe to retry.

Programmatic Access

Automatic Invalidation

The cache is automatically invalidated when:

Event Trigger Action
RolesAssigned assignRole(), syncRoles(), removeRole() Rewarm user + reindex role-to-users mapping
PermissionsAssigned givePermissionTo(), revokePermissionTo(), syncPermissions() on a user Rewarm user
PermissionsSynced Role permissions updated Rewarm role + all users with that role
RoleDeleted Role::delete() Evict role + rewarm affected users
UserDeleted User model deleted (via DispatchesPermissionEvents trait) Evict user cache
Login User logs in (if warm_on_login enabled) Warm user cache

To auto-dispatch UserDeleted, add the trait to your User model:

Rate-limited cache-miss warming

When a permission check lands on a user whose cache is missing in Redis, PermissionResolver triggers warmUser() automatically. To protect the database from warm storms (e.g., Redis is cold after a restart and thousands of simultaneous requests hit the same user), the resolver records each warm attempt and suppresses retries for resolver_warm_cooldown seconds (default 1.0s). This is in-memory per worker; tune it via config or the PERMISSIONS_REDIS_WARM_COOLDOWN env variable.

Multi-tenant deployments

When TenantAwareRedisPermissionRepository is registered, all user and role cache keys are prefixed with the current tenant ID (t:{id}:*). Permission group metadata is intentionally stored globally (un-prefixed) because permission definitions live in the shared permissions table and are not tenant-specific.

Database Seeding

Define your roles and permissions in config/permissions-redis.php and seed them with a single command:

The command creates all permissions referenced under roles automatically. Existing permissions and roles are left unchanged in incremental mode (default). The --fresh flag deletes all existing permissions, roles, and pivot assignments before seeding.


UUID / ULID Support

This package supports integer, UUID, and ULID primary keys for your User model. Set the column type in your configuration before running migrations:

Value Column Type Use Case
'int' unsignedBigInteger Default auto-increment IDs
'uuid' uuid UUID primary keys (HasUuids trait)
'ulid' ulid ULID primary keys (HasUlids trait)

All APIs (hasPermissionTo, assignRole, middleware, Blade directives, etc.) work transparently with any ID type. No code changes required beyond the config setting.

Note: If you have already run migrations with 'int' and need to switch to UUID/ULID, you will need to create a new migration to alter the model_id columns in the pivot tables.


Laravel Octane

When running under Laravel Octane, singletons persist across requests. This package provides built-in support to flush in-memory caches between requests, preventing stale permission data from leaking.

Setup

Or in your config:

When enabled, the package listens to RequestReceived and automatically:

  1. Flushes the PermissionResolver in-memory caches (permission, role, super admin)
  2. Resets the RedisPermissionRepository cached connection, prefix, and TTL

This ensures each Octane request starts with a clean state while still benefiting from Redis-level caching.


Multi-Tenancy

The package supports multi-tenant applications by prefixing Redis keys with a tenant identifier, ensuring complete isolation of permission data between tenants.

Setup

Or in your config:

Built-in Resolvers

Resolver Description
'stancl' Auto-detects tenant via stancl/tenancy
App\Tenancy\MyResolver::class Custom callable class returning string\|int\|null
null Disabled (default)

Custom Resolver

Create a class that returns the current tenant identifier:

How It Works

When tenancy is enabled, user-related Redis keys are prefixed with the tenant ID:

Role-related keys are prefixed the same way (auth:role:t:acme:5:permissions), so cached role data is isolated per tenant even though role definitions live in the shared database tables.


Integrations

This package works well with Laravel Policies, Sanctum/Passport, and Pulse. See the full integrations guide for detailed examples.

Laravel Policies

Policies work automatically with this package. The Gate before callback checks Redis first. If the user has the permission, it's granted immediately. If not, the Policy method decides:

Laravel Sanctum / Passport

Combine token abilities with Redis permissions for API routes:

For dual enforcement (token ability + user permission), see the custom middleware example in the integrations guide.

Laravel Pulse

Track permission check performance with a custom Pulse recorder. The integrations guide includes a complete setup with recorder, decorator, and dashboard card. No Pulse dependency is required in this package.


Conventions

Permission Naming

Use dot notation with the pattern resource.action:

Permission Groups

Group related permissions for organizational clarity:

Role Naming

Use lowercase snake_case:

Guard Names

Permissions and roles are scoped by guard. The default is 'web'. If you use multiple guards (e.g., api), specify the guard explicitly:

Fluent Guard Scoping

Use forGuard() to scope a single check without passing the guard to every method:

Enum Support

You can use BackedEnum for type-safe permission/role references:

Direct vs. Role-Based Permissions

Permission checks always see the merged set. When business logic needs to distinguish the two sources at runtime, use getDirectPermissions() / getPermissionsViaRoles(); both read the Eloquent relations (SQL), not the Redis cache.


API Reference

HasRedisPermissions Trait: Check Methods

Method Signature Returns Description
hasPermissionTo hasPermissionTo(string\|BackedEnum $permission, ?string $guardName = null) bool Check if user has a specific permission
hasAnyPermission hasAnyPermission(mixed ...$permissions) bool Check if user has any of the given permissions
hasAllPermissions hasAllPermissions(mixed ...$permissions) bool Check if user has all of the given permissions
hasRole hasRole(mixed $roles, ?string $guardName = null) bool Check if user has a role (accepts string, int, BackedEnum, array, Collection)
hasAnyRole hasAnyRole(mixed ...$roles) bool Check if user has any of the given roles
hasAllRoles hasAllRoles(mixed ...$roles) bool Check if user has all of the given roles

HasRedisPermissions Trait: Retrieval Methods

Method Signature Returns Description
getAllPermissions getAllPermissions(?string $guard = null) Collection<PermissionDTO> All permissions (direct + role-inherited) as DTOs, from Redis
getPermissionNames getPermissionNames(?string $guard = null) Collection<string> Permission names only, from Redis
getRoleNames getRoleNames(?string $guard = null) Collection<string> Role names, from Redis
getDirectPermissions getDirectPermissions(?string $guard = null) Collection<Permission> Directly assigned permissions only, read from the database (SQL)
getPermissionsViaRoles getPermissionsViaRoles(?string $guard = null) Collection<Permission> Role-inherited permissions only, deduplicated, read from the database (SQL)
forGuard forGuard(string $guard) static Fluent guard scope (auto-resets after one call)

HasRedisPermissions Trait: Assignment Methods

Method Signature Returns Description Event
assignRole assignRole(mixed ...$roles) static Add roles (does not remove existing) RolesAssigned
syncRoles syncRoles(mixed ...$roles) static Replace all roles RolesAssigned
removeRole removeRole(mixed $role) static Remove a specific role RolesAssigned
givePermissionTo givePermissionTo(mixed ...$permissions) static Add direct permissions none
revokePermissionTo revokePermissionTo(mixed ...$permissions) static Remove direct permissions none
syncPermissions syncPermissions(mixed ...$permissions) static Replace all direct permissions none

All assignment methods accept: string, int (ID), BackedEnum, array, or Collection.

HasRedisPermissions Trait: Relationships & Scopes

Method Signature Returns Description
roles roles() BelongsToMany<Role> Eloquent relationship to roles
permissions permissions() BelongsToMany<Permission> Eloquent relationship to direct permissions
scopeRole scopeRole($query, mixed $roles, ?string $guard = null) Builder Filter users by role
scopePermission scopePermission($query, mixed $permissions, ?string $guard = null) Builder Filter users by permission (direct or via role)

Role Model Methods

Method Signature Returns Event
Role::findOrCreate findOrCreate(string $name, string $guardName = 'web') Role none
syncPermissions syncPermissions(mixed ...$permissions) static PermissionsSynced
givePermissionTo givePermissionTo(mixed ...$permissions) static PermissionsSynced
revokePermissionTo revokePermissionTo(mixed ...$permissions) static PermissionsSynced
permissions permissions() BelongsToMany<Permission> none
users users() BelongsToMany<User> none

Deleting a role dispatches RoleDeleted and automatically rewarms affected users' caches.

Permission Model Methods

Method Signature Returns
Permission::findOrCreate findOrCreate(string $name, string $guardName = 'web', ?string $group = null) Permission

Updating or deleting a permission automatically rewarms all affected users' caches.

AuthorizationCacheManager

Method Signature Returns Description
warmAll warmAll() void Flush Redis + reload all users and roles from DB
rewarmAll rewarmAll() void Reload without flushing (incremental)
warmUser warmUser(int\|string $userId) void Warm a specific user's cache
warmRole warmRole(int $roleId) void Warm a specific role's cache
evictUser evictUser(int\|string $userId) void Remove a user from Redis cache
evictRole evictRole(int $roleId) void Remove a role from Redis cache
warmPermissionAffectedUsers warmPermissionAffectedUsers(int $permissionId) void Rewarm all users affected by a permission change

Exceptions

Exception HTTP Status Thrown By Description
UnauthorizedException::forPermissions($perms) 403 PermissionMiddleware User lacks required permissions
UnauthorizedException::forRoles($roles) 403 RoleMiddleware User lacks required roles
UnauthorizedException::forRolesOrPermissions($items) 403 RoleOrPermissionMiddleware User lacks both roles and permissions
UnauthorizedException::notLoggedIn() 401 All middleware User is not authenticated

All exceptions extend Symfony\Component\HttpKernel\Exception\HttpException. Use getRequiredItems() to retrieve the missing roles/permissions:

Events

Event Payload Dispatched When Cache Action
RolesAssigned Model $user assignRole(), syncRoles(), removeRole() Rewarms user cache + reindexes role-to-users mapping
PermissionsAssigned Model $user givePermissionTo(), revokePermissionTo(), syncPermissions() on a user Rewarms user cache
PermissionsSynced Model $role $role->syncPermissions(), givePermissionTo(), revokePermissionTo() Rewarms role + all users with that role
RoleDeleted int $roleId, array $affectedUserIds Role::delete() Evicts role cache + rewarms affected users
UserDeleted int\|string $userId Manually dispatched Evicts user cache from Redis

All events are in the Scabarcas\LaravelPermissionsRedis\Events namespace and use the Dispatchable trait.

Artisan Commands

Command Description
permissions-redis:warm Warm the full authorization cache
permissions-redis:warm-user {userId} Warm cache for a specific user
permissions-redis:flush Flush all authorization cache (requires confirmation)
permissions-redis:stats Display cache statistics (cached users, roles, keys, TTL)
permissions-redis:seed Seed permissions and roles from config
permissions-redis:migrate-from-spatie Migrate data from spatie/laravel-permission

Testing

The package uses Pest for testing and provides an InMemoryPermissionRepository for testing without Redis:

Testing in Your Application

WithPermissions Trait

The package provides a WithPermissions trait with helpers for feature tests:

Available methods:

Method Description
seedPermissions(['posts.create', ...]) Create permission records in the database
seedRoles(['admin' => ['posts.*'], ...]) Create roles with permission assignments
actingAsWithPermissions($user, [...]) Seed + assign permissions + actingAs()
actingAsWithRoles($user, [...]) Seed + assign roles + actingAs()
flushPermissionCache() Flush both Redis and in-memory caches

Pest Example

InMemoryPermissionRepository

Alternatively, bind the in-memory repository in your test suite to avoid Redis entirely:


Comparison with spatie/laravel-permission

Feature Comparison

Feature spatie/laravel-permission laravel-permissions-redis
Core
Assign roles to users ✅ ✅
Assign permissions to roles ✅ ✅
Assign direct permissions to users ✅ ✅
Multiple guard support ✅ ✅
Fluent guard scoping (forGuard()) ❌ ✅
BackedEnum support for roles/permissions ✅ ✅
UUID / ULID primary keys ✅ ✅
Querying
hasRole / hasAnyRole / hasAllRoles ✅ ✅
hasPermissionTo / hasAnyPermission / hasAllPermissions ✅ ✅
getDirectPermissions() ✅ ✅ (SQL read)
getPermissionsViaRoles() ✅ ✅ (SQL read)
Query scopes (scopeRole, scopePermission) ✅ ✅
Cache
Cache backend Laravel Cache (any driver) Redis directly (SET structures)
Permission check complexity O(n) array scan O(1) SISMEMBER
In-memory request cache ❌ ✅
Automatic cache invalidation Forget all on change Surgical warm per user/role
Cache warm on login ❌ ✅
Cache warm CLI ❌ ✅ (permissions-redis:warm)
Cache statistics CLI ❌ ✅ (permissions-redis:stats)
Middleware & Blade
permission / role / role_or_permission middleware ✅ ✅
AND (&) / OR (\|) operators in middleware ✅ ✅
@role / @hasanyrole / @hasallroles ✅ ✅
@permission / @hasanypermission / @hasallpermissions ❌ ✅
@unlessrole / @unlesspermission Roles only ✅ (close with @endrole / @endpermission)
Gate integration (@can, authorize()) ✅ ✅
Advanced
Wildcard permissions (fnmatch) Partial ✅
Super admin role (bypass all checks) ❌ ✅
Laravel Octane support ❌ ✅
Multi-tenancy (Redis key isolation) Teams feature ✅
Permission groups ❌ ✅
Testing helpers trait ❌ ✅ (WithPermissions)
Migration from Spatie CLI ❌ ✅
Requirements
PHP 8.0+ 8.3+
Laravel 8-13 11-13
Redis Optional Required

Performance Benchmark

A standalone benchmark application compares both packages side by side, run with php artisan bench:markdown 1,10,50 --warm=5 --runs=30 (Apple Silicon, PHP 8.x, predis, local Redis). Each "request" resets in-memory state, so neither package carries PHP process memory across requests (matching PHP-FPM, and Octane where this package resets its in-memory cache per request). The hardware-independent result is the query count (4 queries hitting several tables versus 1). Wall-clock is modest and depends heavily on your database, so read the latency tables with that in mind.

Database queries per authorization-heavy request

Scenario spatie/laravel-permission laravel-permissions-redis Reduction
1 request (37 checks) 4 DB queries 1 DB query 75%
10 requests 40 DB queries 10 DB queries 75%
50 requests 200 DB queries 50 DB queries 75%

Spatie caches the global permission/role registry, but each User::find() still lazy-loads that user's role and permission relations from the database (about 4 queries across several tables). This package keeps the resolved per-user set in Redis, so the only database query left is the SELECT ... FROM users lookup; the rest is served from Redis plus a per-request in-memory cache.

Wall-clock per request (median, p50)

On in-process SQLite (database queries are nearly free, which isolates the Redis/in-memory paths):

Scenario spatie/laravel-permission laravel-permissions-redis Speedup
1 request (37 checks) 2.22 ms 1.70 ms ~1.3x
10 requests 23.0 ms 16.4 ms ~1.4x
50 requests 118 ms 82 ms ~1.45x

On MySQL over TCP (a realistic networked database; numbers are noisy across runs):

Scenario spatie/laravel-permission laravel-permissions-redis Speedup
1 request (37 checks) ~3.5 ms ~3.8 ms ~1.0x (often a wash)
10 requests ~36 ms ~36 ms ~1.0x
50 requests ~185 ms ~140 ms ~1.3x

Each request exercises 27 hasPermissionTo calls, 4 hasRole calls, 4 batch ops, and 2 collection calls. On SQLite, avoiding 3 relation queries shows up as ~1.4x. On a networked database this package replaces those DB queries with several Redis reads (SMEMBERS / HMGET) over the same network, so latency lands close to Spatie plus cache and the edge is small and noisy (clear only on authorization-heavy requests). It was never the order of magnitude older numbers implied. The durable, hardware-independent difference is the query count above; the latency benefit is real but modest and database-dependent.

How the two approaches differ

Aspect spatie/laravel-permission laravel-permissions-redis
What's cached globally Permission/role registry (in the Cache store) Same data, denormalized into per-user/per-role Redis SETs
Per-user resolution Lazy-loads the user's role/permission relations from the DB each request Reads the resolved per-user set from Redis
Permission check In-memory check once the relations are loaded SISMEMBER against a Redis SET, then in-memory for the rest of the request
Invalidation forgetCachedPermissions() drops the whole registry cache Surgical: rewarms only the affected user/role
After a change Next requests rebuild the registry from the DB Other users' caches are untouched

Where it actually helps

If your app runs a handful of checks per request at low traffic, Spatie with its cache is perfectly adequate and simpler (no hard Redis dependency). See When to Use This Package.

Try it yourself

See the benchmark repository for full details, screenshots, and instructions.

When to Use This Package

Use laravel-permissions-redis when:

Stick with spatie/laravel-permission when:


Migrating from Spatie

Coming from spatie/laravel-permission? The API is intentionally similar. Most changes are namespace swaps and config adjustments.

See the full migration guide for:


Troubleshooting

Redis connection refused

Symptom: ConnectionException: Connection refused or Redis server went away.

Fix:

  1. Verify Redis is running:

  2. Check your .env Redis settings:

  3. Verify the connection name in config/permissions-redis.php matches a connection defined in config/database.php:

  4. If using Docker, ensure the Redis service is accessible from your app container (use the service name, not 127.0.0.1).

Cache not invalidating after changes

Symptom: assignRole() or givePermissionTo() succeeds but hasRole() / hasPermissionTo() still returns the old value.

Fix:

  1. Verify the event subscriber is registered. The CacheInvalidator subscriber should be active:

  2. Check that you are not suppressing events. Model::withoutEvents() or Event::fake() will prevent cache invalidation:

  3. If you modify pivot tables directly via DB::table(), the cache won't auto-update. Warm manually:

  4. For Octane users, ensure reset_on_request is enabled so in-memory caches don't persist across requests:

Permissions not detected after assignment

Symptom: $user->hasPermissionTo('posts.edit') returns false even though the permission was just assigned.

Fix:

  1. Guard mismatch (the most common cause): Permissions and roles are scoped by guard name. If a permission was created with guard web but you're checking from an api guard context, it won't match:

  2. Permission not assigned to role. If the user has a role but the role doesn't have the permission:

  3. Cache is stale. Force a cache refresh:

Performance degradation

Symptom: Permission checks are slower than expected.

Diagnose:

  1. Check cache statistics:

    • 0 cached users = cache is cold. Run php artisan permissions-redis:warm.
    • TTL too low = cache expires too often. Increase PERMISSIONS_REDIS_TTL.
  2. Check Redis latency:

    Latency above 1ms may indicate network issues between app and Redis.

  3. If using predis (userland PHP client), consider switching to phpredis (C extension) for better performance:

    Then set 'client' => 'phpredis' in config/database.php.

  4. If wildcard permissions are enabled and you have many permissions per user, fnmatch() scans all permissions. Consider using explicit permissions instead of wildcards for hot paths.

Conflict with spatie/laravel-permission

Symptom: Unexpected behavior when both packages are installed, or errors after removing Spatie.

Fix:

  1. Both installed (during migration): Both packages register the same middleware aliases (permission, role, role_or_permission). Whichever service provider loads last wins. This is expected during migration. Follow the migration guide.

  2. After removing Spatie:

  3. Remove Spatie's config if it still exists:

  4. Search for leftover Spatie references:

Migrations not publishing

Symptom: vendor:publish doesn't create migration files or config.

Fix:

  1. Verify the package is installed:

  2. Verify service provider auto-discovery is working:

  3. Publish by tag explicitly:

  4. If using dont-discover in composer.json, register the service provider manually in bootstrap/providers.php:

Versioning

This package follows Semantic Versioning. The early 1.x through 4.x line moved fast and shipped breaking changes more often than it should have; from 4.x onward the API is considered stable, and breaking changes will only land in a new major (5.0.0). Minor releases add backwards-compatible features, patch releases are fixes only. If a release breaks something that this policy says it shouldn't, please open an issue, it's a bug.


License

This package is open-sourced software licensed under the MIT License.


Acknowledgements

This package is heavily inspired by spatie/laravel-permission by Spatie. Their work established the conventions and API patterns that the Laravel community knows and trusts. This package builds on those foundations, re-engineering the storage and resolution layer to use Redis as the primary cache with automatic invalidation.

Key concepts inherited from Spatie:


Author

Sebastian Cabarcas | [email protected]


All versions of laravel-permissions-redis with dependencies

PHP Build Version
Package Version
Requires php Version ^8.3
illuminate/support Version ^11.0|^12.0|^13.0
illuminate/database Version ^11.0|^12.0|^13.0
illuminate/redis Version ^11.0|^12.0|^13.0
illuminate/events Version ^11.0|^12.0|^13.0
illuminate/auth Version ^11.0|^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package scabarcas/laravel-permissions-redis contains the following files

Loading the files please wait ...