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

Vendra Permission Laravel Package

misaf/vendra-permission

Role and permission management for Vendra: define roles, assign granular permissions, and gate access across your Laravel application. Integrates with Vendra's admin panel and Laravel's authorization layer to keep user access organized and secure.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the parent repo first. misaf/vendra-permission is a READ ONLY subtree split of misaf/vendra. The split repo is for Composer consumption and browsing; issues, PRs, and the real history live upstream. Before you write a line of code, clone misaf/vendra and look at packages/vendra-permission. Two things live only there and matter more than the split:

  • skills/vendra-permission-development/SKILL.md — the module's own conventions (namespace, boundaries, Filament layout, tenant rules). Treat it as the spec.
  • Sibling packages misaf/vendra-support (tenancy, TenantResolver) and misaf/vendra-tenant. This package is tenant-agnostic but tenant-ready; understanding the resolver is non-optional.

Inside this package, read in this order:

  1. composer.json — the support-layer dependency is the real contract. Note the self.version alignment across the Vendra graph.
  2. src/VendraPermissionServiceProvider.php — what it publishes, what it registers (TenantTableRegistry entries, the Filament Plugin), and which middleware aliases exist.
  3. config/ — model overrides, table names, guard, caching toggle.
  4. database/migrations/ — the create migration conditionally adds tenant_id via TenantSchema; read it before you plan any schema work.
  5. src/Models/{Role,Permission}.php — these extend the Spatie Permission models, so the public API you'll use day to day is largely Spatie's.

Minimal install (assuming a Vendra host app):

composer require misaf/vendra-permission
php artisan vendor:publish --tag=vendra-permission-config
php artisan vendor:publish --tag=vendra-permission-migrations
php artisan migrate

Add the trait to your authenticatable model (verify the trait name in src/Concerns — it is the Spatie-compatible one, not a bespoke API):

use Misaf\VendraPermission\Concerns\HasRoles;

final class User extends Authenticatable {
    use HasRoles;
}

Then confirm what the package actually gives you before assuming:

php artisan list | grep -i permission   # cache reset, seeding helpers, etc.
php artisan route:list --middleware     # confirm middleware aliases registered

First use case — pick the boring one. Don't start with tenancy or the Filament UI. Start with a single guarded route:

Route::post('/posts/{post}/publish', PublishPost::class)
    ->middleware('permission:publish posts');

Seed exactly two permissions and one role, log in as the role holder, and prove the deny path first (403 as a user without the role). If deny doesn't work, nothing downstream matters.

Implementation Patterns

Seed permissions as code, in this package's seeder. Permission names are a public contract — controllers, middleware, Blade, Filament, and API resources all hardcode them. Create them in a versioned seeder, never by hand in tinker:

final class PermissionSeeder extends Seeder
{
    public function run(): void
    {
        foreach (['posts.view', 'posts.publish', 'posts.delete'] as $name) {
            Permission::findOrCreate($name, 'web');
        }

        Role::findOrCreate('editor', 'web')->syncPermissions(['posts.view', 'posts.publish']);
    }
}

Name with resource.action (dot or space, pick one and enforce it in review — Spatie accepts both, mixed conventions become unfixable once seeded).

Prefer the Gate over bespoke plumbing. The package assigns roles/permissions; Laravel's Gate is what enforces them. In controllers and policies use $this->authorize('publish', $post) / $user->can('posts.publish'), not $user->hasPermissionTo(...). That keeps the package swappable and keeps super-admin bypass in one place:

// AppServiceProvider::boot()
Gate::before(fn (User $user) => $user->hasRole('super-admin') ? true : null);

Return null, never false, from Gate::before — otherwise you silently deny everything for everyone else.

Middleware for routes, Blade for affordances, both from the same key.

@can('posts.publish')
    <x-button>Publish</x-button>
@endcan

Never hide a button without also guarding the route. A hidden button is UX; a middleware check is security.

Tenancy: don't implement it yourself. If the host has tenancy enabled, TenantSchema::addTenantColumn() puts tenant_id on the permission tables and BelongsToTenant assigns it on creating from the bound TenantResolver. Your job is only to ensure the tables are registered so vendra-tenant:enable {tenant} can retrofit pre-existing installs:

// ServiceProvider — preserve configured table names and connections
TenantTableRegistry::register(config('permission.table_names.roles'));
TenantTableRegistry::register(config('permission.table_names.permissions'));

For team-scoped roles (per-tenant role assignment rather than per-tenant role definitions), use Spatie's team support: set permission.teams and the team foreign key in config before migrating, and call setPermissionsTeamId($tenantId) in middleware or a job boundary — not in a model accessor.

Filament admin UI is the day-to-day surface. The module ships a Plugin with cluster resources under src/Filament/Clusters/Resources/. Register the plugin in the panel — never wire resources manually:

$panel->plugin(VendraPermissionPlugin::make());

When extending those resources, keep them thin: form in Schemas/*Form.php, table in Tables/*Table.php, and eager-load role relations (->withCount('permissions'), ->with('roles')) because a permission table with lazy-loaded relations is a classic N+1.

Testing pattern. Follow the module suite:

php artisan test --compact --testsuite=vendra-permission
composer stan

Write both allow and deny cases for every ability you introduce. A permission test that only asserts "allowed" is a test that passes when the package is removed entirely.

Caching on in production, off in tests. Permission checks are cached; enable the cache in config/permission.php for production and leave the array/file driver in tests so cache state can't leak between cases. Clear on deploy as part of the release command, not as an afterthought:

php artisan permission:cache-reset   # verify the exact command name against `artisan list`

Gotchas and Tips

You cannot fix bugs here. The split repo is read-only. Any fix, migration change, or Filament tweak goes to misaf/vendra under packages/vendra-permission. Opening a PR against this repo will fail. Plan your contribution workflow accordingly — and note there's a real maintenance signal problem: 1 star, and a four-release jump (1.11.0 → 1.12.3) with no descriptive release notes. Before adopting, read the diff upstream and check for CVEs/GHSAs on the intermediate patches. Authorization packages are privilege-escalation targets.

Stale permission cache is a security incident, not a bug. After revoking a role, a user can keep access until the cache clears. Put permission:cache-reset in your deploy pipeline, call it from any admin action that mutates roles, and write a runbook entry. Test it explicitly: revoke → assert 403 immediately, with production cache config.

Guard mismatch fails silently. Permissions are created per guard (web vs api). Create under the wrong guard and $user->can() returns false with no error. Keep the guard in config and pass it consistently in every findOrCreate/syncPermissions call.

The migration is conditional — don't hand-edit it. tenant_id appears only when TenantSchema says so. Editing a published migration breaks the retrofit path for existing tenants. If a table needs the column after the fact, use vendra-tenant:enable {tenant}, not a new migration.

Never reference a concrete tenant provider. No Misaf\VendraTenant anywhere in models, migrations, factories, seeders, or fixtures. Derive tenant awareness purely from the bound TenantResolver (TenantAwareness, BelongsToTenant, RequiresCurrentTenant). There is no tenant_aware config toggle — don't invent one.

Don't put permission domain code in the host app. Models, policies, observers, console commands, Filament classes, config, migrations, translations, and tests belong in Misaf\VendraPermission. The host app integrates; it doesn't reimplement. The one legitimate host-app artifact is the trait use on User.

Translatable fields must be JSON columns. If a field is in $translatable, it is a JSON column, full stop — with translation-aware casts, validation, factories, Filament locale UI, and tests. Fields not in $translatable must be scalar and must not use translatable traits or arrive as translation-shaped arrays. Getting this wrong is a migration you can't cleanly reverse.

Filament specifics that bite:

  • Filament v5 namespaces: Filament\Forms\Components, Filament\Schemas\Components (layout), Filament\Tables\Columns, Filament\Actions, Filament\Support\Icons\Heroicon. Older snippets from tutorials will not resolve.
  • Cluster resources stay under Clusters/Resources/ with the matching namespace; ungrouped, with $navigationSort taken from a package NavigationPriority case. Never hardcode numeric sorts.
  • Labels come from vendra-permission::attributes / ::navigation, with separate singular and plural keys in en, de, and fa. Navigation labels ≤ 24 characters — Farsi and German will blow past that if you eyeball it in English only.

Config quirks worth verifying against your version: table names, model class overrides, cache store, and team scoping are all config-driven, and the package's own Filament resources read the same config. Change a model class in config and you must also confirm the module's resources still resolve — a mismatch produces confusing "class not found" errors inside the admin panel rather than at boot.

Extension points, in order of safety: (1) the Plugin for UI, (2) Gate::before/policies for logic, (3) config model swaps for behavior, (4) a private fork for everything else. For a package this small, a documented fork is a realistic fallback — but decide that before adoption, not during an incident, and pin the exact version you reviewed.

Onboarding cost is small; trust is not. A Laravel-fluent engineer is productive in a day. Require a second reviewer on any change that alters permission resolution or cache semantics, and front-load the allow/deny regression suite — it is the artifact that makes every future upgrade cheap.

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
shitoudev/permissionmanager