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

Laravel Governor Laravel Package

genealabs/laravel-governor

Laravel Governor adds flexible role- and permission-based authorization to Laravel apps. Define roles and abilities, assign them to users, and protect routes and views with middleware and Blade helpers for simpler access control.

View on GitHub
Deep Wiki
Context7

Getting Started

Laravel Governor is a database-backed RBAC layer. The first hour is spent proving the four moving parts line up — config, migrations, the user trait, and the middleware — before you write any role data.

1. Install pinned, not floating. This is a 0.x package on a security-critical path:

composer require genealabs/laravel-governor:0.22.0

Exact version, no caret. composer update --dry-run afterwards and confirm the resolved illuminate/* and php constraints actually include your production versions — a release note saying "added Laravel 13 support" can coincide with older majors silently dropping out.

2. Discover what it publishes rather than guessing provider names.

php artisan vendor:publish

Pick the Governor provider from the interactive list, then read the published config/governor.php end to end. That file is the package's real documentation: it tells you which models, table names, cache store, and authorization behaviours you can swap.

3. Review the migrations before running them. Check for the roles, permissions/abilities, and pivot tables, and note whether they are additive-only. On a greenfield DB run php artisan migrate; on an existing app, migrate a staging clone first and diff the schema.

4. Add the roles trait to your user model. The trait lives under vendor/genealabs/laravel-governor/src/Traits — list that directory and use what is actually there rather than copying a trait name from a README that may lag the code:

use GeneaLabs\LaravelGovernor\Traits\Governable; // verify name in src/Traits

class User extends Authenticatable
{
    use Governable;
}

5. Seed the baseline matrix. Roles belong in a versioned seeder so they are auditable in git; assignments belong in the database so admins can change them at runtime. Do not hand-create roles in tinker and call it done — you will not be able to rebuild an environment.

6. First use case — protect one route group and prove both directions.

Route::middleware(['auth', 'governor'])->group(function () {
    Route::get('/reports', ReportController::class);
});

Write two tests: a user without the role gets 403, the same user after assignment gets 200. That single pair exercises config, migration, trait, cache, and middleware together. If it passes, the integration is real; if it fails, you have found the problem on day one instead of in staging.

Where to look first, in order: config/governor.php → published migrations → src/Traits → the middleware class → any admin controller/UI the package ships. The documentation is thin relative to the trust you are placing in this code, so budget the spike to read the source, not just the README.

Implementation Patterns

Two layers, one source of truth for each. Use Governor for the coarse question — "is this user an admin, an editor, a billing manager" — and keep Laravel's native Gates and Policies for the conditional question — "can this user edit this invoice". Wire the coarse layer once:

Gate::before(function (User $user, string $ability) {
    return $user->hasRole('super-admin') ? true : null;
});

Returning null (not false) for the non-match is what lets the native policy still run. This is the pattern that makes the package additive to existing authorization instead of a rival to it.

Wrap it behind your own contract. The single highest-leverage decision with a pre-1.0, single-maintainer dependency:

interface AuthorizationService
{
    public function allows(User $user, string $ability, ?Model $subject = null): bool;
    public function assignRole(User $user, string $role): void;
}

Bind a Governor-backed adapter. Every call site in your app depends on the interface, so swapping to Spatie or raw Gates later is one class, not a grep-and-replace across controllers, middleware, and Blade templates.

Gate at the route group, not per route. One middleware on a group is auditable; forty middleware calls scattered across routes/web.php will drift. Pair each group with a named ability so the permission string in the database and the route file can be cross-checked by eye.

Blade for UI, middleware for enforcement. Use the package's Blade directives to hide links and buttons, and never rely on that alone. Hidden UI is not denied access — the route must be gated independently. Assume every hidden button is reachable by a curl.

Versioned seeder, runtime assignments. Roles and abilities in code (reviewable, reproducible); user-role assignments in the database (admin-editable, the whole point of the package). Seed idempotently with firstOrCreate so re-running doesn't duplicate the matrix.

Entity-scoped roles for multi-tenant work. If workspaces, team accounts, or partner tiers are on the roadmap, decide the scoping key now — a role assignment that is global today is a data migration tomorrow. Model per-workspace membership explicitly rather than encoding tenancy in role names like acme-admin.

Admin surface is a privileged resource. If you ship a role-management screen, gate it behind its own ability, log every change, and consider a custom screen over any bundled UI — it is the highest-value target in the app. An user who can edit roles can grant themselves anything.

Test the matrix, not the happy path. Seed roles in a test case base class, flush the permission cache between tests, and cover: allow per role, deny per role, cache invalidation after assignment, and middleware behaviour for guests. Authorization bugs are almost always "should have been denied", so weight the deny cases.

Gotchas and Tips

Permission caching is the correctness hazard, not the performance feature. Roles and abilities are cached; a missed invalidation across nodes is a privilege-escalation bug on grant and a lockout bug on revoke, and neither reproduces locally. Design the invalidation strategy — tagged cache with Redis, or explicit flush on every write through your AuthorizationService — before the first role is written. If you cannot articulate how node B learns about a change made on node A, stop and fix that first.

Long-lived processes hold stale permissions. Queue workers, Octane, and Swoole keep the container and caches warm across requests. A role revoked in the UI may not take effect in a worker until restart. Either flush explicitly, or keep long-running authorization out of workers entirely.

Fail closed. Confirm what happens on a lookup miss — unknown role name, missing pivot row, empty ability list. Deny by default. If the package's behaviour here is ambiguous, wrap it: make your AuthorizationService::allows() return false on anything unexpected, and log the miss.

Gate::before ordering is undocumented. Any other package or app-level hook touching Gate::before/Gate::after — tenancy, super-admin overrides, audit loggers — changes effective authorization by registration order. Audit the full set of hooks, and if two packages both short-circuit true, you have a silent escalation path.

Do not compare role names in application code. if ($user->hasRole('admin')) scattered around the app re-couples you to the data. Check abilities at call sites; let the role→ability mapping live in the database, where an admin can change it without a deploy.

Changing config models or table names after data exists is a migration. The config lets you swap models and tables; doing so once there are rows in production means writing a data migration, not editing a config value. Decide the names in the spike.

Seeder/DB drift is the quiet killer. If someone adds a role through the UI and it never lands in the seeder, the next environment rebuild silently drops it. Diff production roles against the seeder periodically, and treat the seeder as the canonical baseline with UI additions as documented exceptions.

Pin exact, treat every bump as a migration. 0.22.0, not ^0.22. On each upgrade: read the composer.json diff at the tag, run composer why-not in a branch, re-run migrations on a staging clone, then run the authorization suite. Budget 1–2 engineer-days per bump.

Namespace ambiguity is a real signal. The repository metadata, maintainer namespace, and package name do not line up cleanly, and there are effectively zero downstream dependents. That means you are among the first serious adopters — read the source you ship, and keep a fork-and-patch plan on file.

Extension points to prefer. The config file (models, tables, cache) and a thin adapter class are the safe seams. Avoid subclassing package internals or overriding its middleware; you will be re-applying those patches on every 0.x bump.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource