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

Filament Shield Enhanced Laravel Package

agroezinger/filament-shield-enhanced

Enhanced roles and permissions for Filament panels. Extends Filament Shield with a cleaner UI, granular permission management, and flexible configuration, so you can control access across resources, pages, and widgets with less setup and fewer surprises.

View on GitHub
Deep Wiki
Context7

Getting Started

This package layers fine-grained, three-part permissions ({Prefix}:{Action}:{Subject}) on top of Filament Shield for subjects that vanilla Shield doesn't cover — Pages, Resources, and as of v0.2.0, arbitrary Livewire components that don't belong to a panel.

Minimal setup:

  1. Install and configure filament-shield first (this addon assumes Shield's config, Permission model, and policy setup are already in place), then composer require this package.
  2. Publish the config: php artisan vendor:publish --tag="shield-enhanced-config" (check vendor/ for the exact tag — the package publishes both a config and an optional RoleResource form stub).
  3. Add the traits to your User model (or whatever model uses HasRoles):
use HasPageShield;
use HasResourceShield;
use HasComponentShield;
  1. Implement the declaration methods, e.g. getShieldComponentPermissions() returning an array/collection of components or subjects you want keys generated for.
  2. Run the generators, in order: shield:generate-enhanced-pages, shield:generate-enhanced-resources, and now shield:generate-enhanced-components. These create the actual Permission rows; without them every canShield() call is a false (and, pre-0.2.0, potentially an exception — see Gotchas).
  3. First use case: gate a page or component in a policy or Blade view:
// in a policy
public function view(User $user, SomePage $page): bool
{
    return $user->canShield('view', $page);
}

// or authorize directly
$user->authorizeShield('update', $component);
@can('view', $record) ... @endcan

Where to look first: the trait source for the three Has*Shield traits (they define the declaration method names and the resolution order), then ComponentPermissionKeyBuilder (the single place that decides how a key string is composed — everything else depends on this matching exactly).

Implementation Patterns

One trait per category, one declaration method each. Keep the declaration methods as the single source of truth for what permissions exist. HasPageShield/HasResourceShield derive subjects from Filament's panel registry, while HasComponentShield derives them from config('...components.scan_paths'). Because components have no registry, treat scan_paths as a curated list — point it at app/Livewire/Shared, app/Livewire/Widgets, or similar directories rather than app/, or you'll generate keys for components that are never permission-gated.

Generator in the deploy pipeline. Add all three shield:generate-enhanced-* commands to your deploy script (after migrations, before cache warmup) so permission rows always match the declarations. This matters more here than with stock Shield because renames silently create orphans rather than failing loudly.

Super-admin / bypass via Gate. Since canShield() ultimately resolves through the Gate, wire your bypass with Gate::before() rather than adding a wildcard permission row:

Gate::before(fn ($user) => $user->hasRole('super_admin') ? true : null);

This keeps bypass logic in one place and works uniformly across pages, resources, and components.

Policies for component gating. For shared widgets dropped into several pages via @livewire(...), a small policy per component (or a single policy with a $subject switch) plus canShield() reads cleaner than scattering checks in the component's mount(). Authorize in mount() — not render() — so unauthorized access fails before any query work.

Role form integration. If you've published Shield's RoleResource, swap in HasEnhancedRoleForm and EnhancedComponentPermissionsForm so the role edit screen pre-fills checkboxes for all three categories. Without HasEnhancedRoleForm, component permissions exist but aren't assignable through the UI — you'd be forced to seed role/permission pivots by hand.

Testing. Use Permission::firstOrCreate + $user->givePermissionTo() in test setup, or better, assert the generator output: a test that runs the generators and diffs the created keys against the declared set catches rename drift in CI before it hits canShield() in production.

Gotchas and Tips

canShield() throwing on missing permissions (fixed in 0.2.0, but know the history). Pre-0.2.0, all three traits preferred Spatie's hasPermissionTo()/hasAnyPermission() when available on the user model. Those throw PermissionDoesNotExist when a name has zero matching rows — exactly what happens after you rename an action or add a declaration before running the generator. v0.2.0 routes through the Gate's can(), which resolves unknown abilities to false, matching vanilla Shield's canAccess(). If you're upgrading from 0.1.x and had defensive try/catch wrappers around canShield(), you can drop them — but audit any code that relied on the throw for fail-fast behaviour, since you'll now fail silently to false instead.

Missing permission rows are now a silent deny. The flip side of the fix: forget to run shield:generate-enhanced-components in a deploy and every gated component returns false with no error. Add a smoke assertion (super-admin passes, a known role passes) to your post-deploy checks.

Component discovery is path-based, not usage-based. There's no registry to iterate, so the generator can't tell "shared widget used in three pages" from "abandoned experiment in app/Livewire". Expect noise if scan_paths is broad, and expect misses for components pulled in from packages. Consider declaring an explicit allowlist in getShieldComponentPermissions() alongside scan paths.

Key format must match byte-for-byte. {Prefix}:{Action}:{Subject} is composed by ComponentPermissionKeyBuilder — same shape as pages. Any manual permission seeding, config prefix change, or role import must use the identical casing and separator. A single : vs :: mismatch yields a permission row that exists in the DB and is never matched by canShield().

Permission cache. Spatie caches the permission table. After running generators in a long-lived process (Octane, queue workers), call app(PermissionRegistrar::class)->forgetCachedPermissions() or restart workers, otherwise your newly generated component permissions won't resolve until the next deploy.

Extension points. ComponentPermissionKeyBuilder is the right seam if you need a different key shape (e.g. namespaced subjects like App\Livewire\Foo::bar). Override it in the container rather than post-processing permission names, so the generator, the role form pre-fill, and canShield() all stay in agreement. Similarly, extend the traits rather than editing vendor code if you need richer declaration logic.

RoleResource coupling. EnhancedComponentPermissionsForm is written against the published RoleResource. If you've customised that resource heavily or are on a Filament major that changed form component APIs, treat the published form as a reference implementation and hand-merge the checkbox section rather than swapping it in wholesale.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core