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

Permit Laravel Package

nahid/permit

nahid/permit is a Laravel package for managing roles and permissions. It provides a simple API to assign roles and permissions to users and check access in routes or controllers, helping you add lightweight ACL to your application.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the package's config and migrations, then run the migration to create the roles/permissions tables:

composer require nahid/permit
php artisan vendor:publish --provider="Nahid\Permit\PermitServiceProvider"
php artisan migrate

Where to look first: config/permit.php. This is the heart of the package — it defines the role → ability matrix the rest of the API reads from. Open it before writing any authorization code; most confusion comes from not knowing whether a permission is resolved from this file or from the database.

Wire the trait into your User model (the package ships a Permitable-style trait — confirm the exact namespace against the installed version, as it has moved between releases):

use Nahid\Permit\Traits\Permitable;

class User extends Authenticatable
{
    use Permitable;
}

First real use case: gate a single route. Register the package middleware (in app/Http/Kernel.php on Laravel ≤10, or in bootstrap/app.php via ->withMiddleware() on Laravel 11+), then protect a route:

Route::post('/posts', [PostController::class, 'store'])
    ->middleware('permit:post.create');

And in a controller or view, check the current user's ability through the package's checker:

if (permit()->can('post.create')) {
    // ...
}

Start with a two-role matrix (admin, user) and three or four abilities. Don't model your whole domain in config on day one — the package is small and you'll want to feel out whether DB-backed assignment or the static config file suits you better.

Implementation Patterns

Static matrix in config, dynamic assignment in the DB. The cleanest pattern with this package is a hybrid: declare the shape of your permissions in config/permit.php (so they're reviewable in version control), but assign roles to users at runtime:

permit()->assignRole('editor', $user);
permit()->attachPermission('publish.post', $user);   // per-user grant
permit()->detachPermission('publish.post', $user);   // revoke

This lets admins change who can do what without a deploy, while the ability names stay curated by developers.

Seed the defaults. Put initial role/permission rows in a seeder so a fresh environment is deterministic:

// database/seeders/PermitSeeder.php
public function run(): void
{
    foreach (['admin', 'editor', 'viewer'] as $role) {
        // create via the package's model/service, or insert directly if no API exists
    }
}

Wrap it behind a single service. Because the package exposes both a facade-ish permit() helper and per-model trait methods, call sites fragment quickly. Introduce one thin class — App\Support\Authorization — that delegates to Permit internally. When (not if) you migrate to Laravel's native Gate/Policies, you change one file instead of two hundred.

Blade. If the installed version doesn't ship a directive, add a local one rather than sprinkling @if(permit()->can(...)) everywhere:

// AppServiceProvider::boot()
Blade::if('permit', fn (string $ability) => permit()->can($ability));
@permit('post.edit')
    <a href="{{ route('posts.edit', $post) }}">Edit</a>
@endpermit

Testing. Use $this->actingAs($user) and assert on the same trait method the middleware uses, so your test exercises the real path:

$user->assignRole('viewer');
$this->actingAs($user)->post('/posts', [])->assertForbidden();

Integration tip: keep the permission string format consistent — dot notation (post.create, post.delete) works well and maps cleanly to a future PostPolicy if you outgrow the package.

Gotchas and Tips

  • Config caching will bite you. After editing config/permit.php, run php artisan config:clear. In production with config:cache, role changes made in the file won't take effect until you re-cache. This is the single most common "the permission isn't working" cause.

  • false vs. absent. A matrix entry set to false is an explicit denial; a missing entry is simply unknown. When a user has multiple roles, merge order matters. Decide up front whether explicit denies win, and encode that decision in your wrapper — don't rely on incidental array-merge order.

  • Permission check caching. If you memoize can() results per request (a good idea), make sure assignRole/attachPermission invalidate the cache in the same request — otherwise a freshly granted ability won't appear until the next HTTP cycle.

  • Laravel 11 middleware registration. There's no Kernel.php anymore. The permit: alias must go in bootstrap/app.php:

    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias(['permit' => \Nahid\Permit\Middlewares\PermitMiddleware::class]);
    })
    
  • Multi-guard apps. The checker resolves the currently authenticated user by default. If you have an admin guard, pass the user explicitly (permit()->can('x', $admin)) rather than relying on the ambient one, or you'll get false negatives that are painful to trace.

  • N+1 on role lists. Rendering a user index with a can() call per row can trigger a query per user if roles load lazily. Eager-load the relations the package defines before the loop.

  • Super-admin bypass. There's no built-in "god mode." Add an explicit Gate::before(fn ($user) => $user->isSuperAdmin() ? true : null) — placed before any Permit check — or you'll end up with an empty permission list on your own admin account.

  • Migration name collisions. The published migration uses generic table names. If you already have a roles table, rename before migrating rather than after.

  • Debugging. In tinker: permit()->can('post.edit') after Auth::login($user). If that's true but the route 403s, the problem is middleware registration, not permissions — check the alias and route group order.

  • Maintenance risk. At 74 stars with the last release in Feb 2024, treat this as a thin convenience layer, not load-bearing infrastructure. Keep all calls behind the wrapper service described above; if the package stalls on a future Laravel major, swapping in native Gate + Policies becomes a one-file change.

  • Extension point: for anything beyond CRUD-style checks — ownership ("can this user edit this post?") — the package has no concept of a resource. Do the ownership check in a Policy or a plain if and reserve Permit for coarse-grained abilities. Trying to encode ownership in permission strings (post.edit.42) is a dead end.

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.
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
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource