Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Clearance Laravel Package

rivalex/clearance

A drop-in Laravel admin panel for spatie/laravel-permission. Manage permissions, roles, guards and settings via Livewire 4 + Flux UI, with contextual authorization, parent-bounded ceiling roles, and optional super admin.

View on GitHub
Deep Wiki
Context7

Getting Started

Three steps to a working panel:

composer require rivalex/clearance
php artisan clearance:install

Then add one trait to App\Models\User — it replaces Spatie's HasRoles (PHP dedupes traits, so keeping both is harmless):

use Rivalex\Clearance\Traits\HasClearance;

class User extends Authenticatable
{
    use HasClearance; // HasRoles + canIn()/hasRoleIn()/hasPermissionIn()
}

Finally, get someone (usually yourself) into the panel:

$user->givePermissionTo(['clearance-access', 'clearance-roles-write']);

and browse to /clearance.

Where to look first. Skip the dashboard and open /clearance/roles — that is where the package's opinions live (scope, ceiling, guard-scoped permission matrix). If your app already has Spatie roles and permissions, do not run clearance:install cold; read docs/migration-from-spatie.md and run php artisan clearance:backfill instead, otherwise you will hit the super_admin name collision path.

First real use case. You have an existing Laravel app with a hard-coded role column or a handful of hasRole('admin') checks. Install Clearance, backfill, and hand /clearance to a non-engineer ops person to curate roles — that is the immediate win. The second win, and the actual reason this package exists, comes later: contextual authorization.

Be aware of the dependency footprint before you start. This pulls in Livewire 4 and Flux UI 2.14+, which is a paid/commercial add-on for most users. If your app is on Livewire 3 (supported) or on Flux 1.x, resolve that first. With 0 stars and 0 dependents you are an early adopter — pin the exact version in composer.json and read the published migrations in database/migrations after install rather than assuming.

Implementation Patterns

Choose routes or embed components

The pre-built routes are the low-effort path and are stable-name based (clearance.home, clearance.roles, …) regardless of route_prefix, so your menus and route() calls survive a prefix change. Swap middleware in config/clearance.php for ['web', 'auth', 'verified'] and you are done.

The embed path is what most teams end up using long-term. Every section is a self-contained Livewire component that picks up the host layout:

<livewire:clearance::roles.role-manager />
<livewire:clearance::permissions.permission-manager />
<livewire:clearance::users.user-clearance-manager :userId="$user->id" />

This is the right call when you want a "Team" page inside your own app shell, or when you want to gate a single section (say, only roles) behind an app-specific Gate in addition to the package's own permission checks. Mount them from a Filament resource, a custom controller, anywhere Blade renders.

Delegate write access, section by section

The permission model is the strongest design decision in the package. Read access is a single clearance-access; writes are one permission per section, so you can create a real "role administrator" who cannot touch guards, settings, or user assignments:

$user->givePermissionTo([
    'clearance-access',
    'clearance-roles-write',
    'clearance-permissions-write',
]);

Every permission-granting path in the panel re-checks these internally — this is not merely a UI-level hide. Note that you cannot hand these out through the panel itself; the clearance-* namespace is reserved and only the install flow can create or assign them. Provisioning happens in code/seeds, which is exactly where you want it.

Contextual authorization — the reason to adopt

Global Spatie roles are a blunt instrument. canIn() lets you say "manager of this store" without inventing a permission name per store:

if ($user->canIn('orders-create', $store)) { ... }
if ($user->hasRoleIn('manager', $store)) { ... }

In views:

@canin('orders-create', $store)
    <button>New Order</button>
@endcanin

Both directives resolve to false for guests rather than throwing, which makes them safe to scatter through partials rendered for anonymous users.

Outside the User model — policies, jobs, queued listeners, API resources — use the service or facade, which work even if the model lacks the trait:

use Rivalex\Clearance\Facades\Clearance;

Clearance::canIn($user, 'orders-create', $store);
$effective = Clearance::resolveFor($user, $store); // Collection<string>

Model the context as a regular Eloquent model implementing ClearanceContextable. Typical candidates: Store, Tenant, Project, Workspace, Team.

Ceiling roles for delegated administration

If you let non-engineers create roles, ceilings stop the obvious escalation: a child role can never hold a permission its parent lacks.

use Rivalex\Clearance\Services\RoleService;

app(RoleService::class)->setParent($childRole, $parentRole);

Enforcement happens on every save, which is the important part — it is not a one-time validation you can route around by editing a RoleMeta row later. The ceiling parent is the authority for what the child may contain, so design the hierarchy deliberately (one admin ceiling, many leaf roles) rather than letting it grow into a tree.

Super admin: keep the bypass off

Default super_admin_gate_bypass is false and that is the correct production setting. Turning it on installs a Gate::before() hook that makes super_admin pass every Gate::allows() / can() / policy check in the entire application, not just Clearance. That silently defeats every policy you wrote. The narrow alternative — grant explicit permissions to super_admin and leave the bypass off — costs a few lines of seeder and preserves your authorization surface.

Guards: DB-backed, injected at boot

Clearance lets you create guards in the panel and writes them into auth.guards at boot time, restricted to an allow-listed driver set. This is genuinely convenient for multi-guard setups (e.g. customer + staff), but understand the trade: guard configuration now lives in the database, not in config/auth.php. Your seeders and any code that assumes config('auth.guards.x') should be aware the value appears at runtime, not at config-cache time.

Opt into the Users module only if you need per-user overrides

modules.users = true unlocks /clearance/user/{id}, where an admin assigns roles globally or scoped to a context, and layers per-context permission overrides. This is the operational counterpart to canIn() — without it, contextual grants have no UI and must be made in code.

Rehearse the install in CI

Your test suite must migrate Spatie before Clearance, or you inherit the same ordering trap the installer exists to solve:

protected function defineDatabaseMigrations(): void
{
    $this->loadMigrationsFrom(database_path('migrations/vendor/spatie'));
    $this->loadMigrationsFrom(database_path('migrations/vendor/clearance'));
}

Or simply run clearance:install as a CI setup step against a throwaway database.

Gotchas and Tips

Never run plain php artisan migrate on a fresh install. Clearance's migrations have a hard dependency on roles/permissions existing. The installer detects the missing Spatie tables and publishes+runs them first; a direct migrate gives you the "run the installer instead" error. Same story in php artisan migrate:fresh scripts and in CI.

On a Spatie app, backfill before install. super_admin, superadmin, super-admin, root, owner are all detected as aliases; the installer prompts to rename in place. In non-interactive contexts (Docker, CI) the prompt blocks — always pass --super-admin-role=NAME explicitly.

forced_off beats global roles. In resolveFor() precedence, a per-context deny removes the permission even if a global role granted it. This is the right semantic for tenancy but surprises people who expect global grants to be inviolable. If your super_admin should still be able to act inside a context where the permission was denied, you need the Gate bypass or a different design — and the bypass has its own cost (see above).

The fine-grained fallback is a footgun in the other direction too. If a clearance-{section}-write row does not exist in the database, the check degrades to clearance-access. On any install that ran clearance:install this never fires — but a manually-truncated permissions table, or a partial backfill, silently widens write access. Verify the five capability rows exist after any manual DB surgery.

Re-running install is additive only. Step 6 never removes permissions from super_admin. That is safe, but it also means a permission you deliberately stripped cannot be re-stripped by re-running the installer — you must remove it again by hand.

Ceilings are enforced on role saves, not on assignments. If you change a parent role's permission set after children exist, review the children — the invariant is maintained at write time, not continuously. Re-saving each child triggers revalidation.

Don't add HasClearance for contextual checks alone. The Blade directives and ContextService work without the trait. If you are wary of swapping traits on a large User model, you can ship contextual authorization with zero model changes and add the trait later.

SVG icons are sanitized twice — on store and on render, against an allow-list. If your icon library uses exotic attributes or embedded <style>, expect silent stripping. Test your default icon set in the settings panel before rolling it out.

Route names survive prefix changes; hard-coded URLs do not. Always use route('clearance.roles') rather than /clearance/roles in your nav, especially if route_prefix is tenant-derived.

Config caching. Run php artisan config:cache after editing clearance.php. Because guards are injected at boot, a stale config cache combined with a guard row added in the panel can produce "guard not defined" errors that look like data corruption but are just cache.

Treat this as a 0.x/early-adopter dependency. 370 tests and 1054 assertions is a serious suite for a package with no external users, but the ecosystem signals are absent: pin the version, read the changelog on every bump (releases and CHANGELOG exist), and keep a written note of which RoleMeta/ContextService behaviours are load-bearing for you so you can fork if maintenance lapses. The clearance-* permission namespace and the clr_* tables are your integration surface — treat table names as semi-public API.

Extension points worth knowing: HasClearance (User), HasPermissionGroups (grouping your permissions in the UI), ClearanceContextable (context models), RoleService::setParent() (ceiling), and the reusable Blade components. There is no published event/hook system beyond these — if you need custom behaviour on role mutation, you are subclassing services, which is why pinning matters.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc