NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

The package caches the permission catalog — id and name only — to keep the Gate check cheap. It auto-invalidates on every grant mutation and on any role or permission save or delete, once that write commits, so you rarely flush it by hand. When you do:

use RoundlyConsulting\Permissions\Facades\Permissions;

Permissions::cache()->forget();

Or from the CLI:

php artisan permissions:cache-reset

Writes inside a transaction

Invalidation follows the commit, not the write. Inside a transaction — a seeder, or a migration on Postgres — the shared cache is flushed when the outermost transaction commits, so a concurrent request can’t re-cache the old catalog in the meantime. Until then the writing process reads the catalog live, so it sees its own changes, and a rollback leaves nothing cached.

The catalog through the facade

Permissions::permissions() and Permissions::exists() read the cached catalog; Permissions::cache() returns a small handle for invalidation:

use RoundlyConsulting\Permissions\Facades\Permissions;

Permissions::permissions();               // cached catalog — Collection<Permission> (id + name)
Permissions::exists('auth.users.view');   // bool — string or BackedEnum, from the cache
Permissions::cache()->forget();           // drop the shared cache entry and this process's memo
Permissions::cache()->flushMemo();        // drop only this process's memo

Use Permissions::findPermission() when you need a full row, description included.

Bulk writes bypass invalidation

Auto-invalidation is driven by Eloquent model events, which mass operations do not fire: Permission::query()->delete(), ::insert(), ::upsert(), DB::table('permissions')->… and truncate(). After seeding or importing that way, invalidate explicitly. Called inside a transaction, forget() flushes now and again when the transaction commits. Permissions::syncFrom() goes through the models, so it needs no flush:

use Illuminate\Support\Facades\DB;
use RoundlyConsulting\Permissions\Facades\Permissions;

DB::table('permissions')->insert([
    ['name' => 'reports.export', 'created_at' => now(), 'updated_at' => now()],
    ['name' => 'reports.schedule', 'created_at' => now(), 'updated_at' => now()],
]);

Permissions::cache()->forget(); // mass writes fire no model events

The short default cache.ttl (300 seconds) bounds how long a missed invalidation can linger — or a catalog that a request was already loading at the moment of the commit. Use a longer TTL only if every write goes through Eloquent.

Octane & queue workers

The package keeps a small per-request memo on top of the shared cache store and resets it at each Octane request, task and tick and as each queued job starts, so a long-lived worker never serves a memo that outlived its authority. Call Permissions::cache()->flushMemo() at any other boundary of a long-lived worker of your own. For invalidation to propagate across workers, use a shared store — redis, database or memcached; the per-worker array store cannot see another worker’s flush:

PERMISSIONS_CACHE_STORE=redis
PERMISSIONS_CACHE_TTL=300

Only scalars are written to the cache — never Eloquent models — so the catalog keeps working on hosts that restrict which classes the cache may unserialize. A payload in an unexpected shape is rebuilt instead of failing the request.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.