Rollen en rechten in Filament werken op drie lagen: canAccessPanel() bepaalt wie het panel in mag, Laravel policies bepalen wat iemand per resource mag, en Spatie laravel-permission met Filament Shield maakt dat beheerbaar via rollen. Shield 4.x werkt met Filament 4 én 5. Belangrijkste valkuil:...
Rollen en rechten in Filament werken op drie lagen: canAccessPanel() bepaalt wie het panel in mag, Laravel policies bepalen wat iemand per resource mag, en Spatie laravel-permission met Filament Shield maakt dat beheerbaar via rollen. Shield 4.x werkt met Filament 4 én 5. Belangrijkste valkuil: zonder policy staat Filament standaard alles toe.
Onze eigen backoffice draait op Filament. Blog, leads, afspraken, cases: alles zit achter een login met canAccessPanel. Zodra meer mensen met verschillende taken toegang krijgen, heb je rollen en policies nodig. Daar gaat dit artikel over. Voor de basis van het framework zelf, zie onze Filament-pagina.
Hoe werkt autorisatie in Filament?
Filament heeft geen eigen rechtensysteem. Het leunt volledig op Laravel: de FilamentUser-contract voor panel-toegang en model policies voor alles daarbinnen.
Concreet zijn er drie lagen:
| Laag | Wat het regelt | Waar je het bouwt |
|---|---|---|
| Panel-toegang | Mag deze user /admin (of een ander panel) in? | canAccessPanel() op je User-model |
| Resource-rechten | Lijst zien, aanmaken, bewerken, verwijderen per model | Laravel policy per model |
| Veld- en actieniveau | Losse velden, kolommen, knoppen | visible(), disabled(), authorize() in je schema of action |
Spatie laravel-permission en Filament Shield zitten niet naast deze lagen. Ze vullen ze in. Shield genereert policies die checken op Spatie-permissions. Filament roept die policies aan. Meer is het niet.
Wat doet canAccessPanel() precies?
canAccessPanel() is de voordeur. Volgens de Filament-docs mag elke App\Models\User lokaal het panel in, maar in productie moet je dit expliciet regelen via de FilamentUser-contract.
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
class User extends Authenticatable implements FilamentUser
{
public function canAccessPanel(Panel $panel): bool
{
return match ($panel->getId()) {
'admin' => $this->hasRole(['super_admin', 'medewerker']),
'portal' => $this->is_klant,
default => false,
};
}
}
Je krijgt het panel als parameter mee. Daarmee scheid je een intern admin panel van bijvoorbeeld een klantportaal in dezelfde applicatie.
Wat niemand je vertelt: deze check draait via de Authenticate-middleware van het panel, op elke HTTP-request. Ook op Livewire-updates. Trek je iemands toegang in, dan vliegt die er bij de volgende klik uit. Dat staat zo in de security-docs van Filament.
Welke policy-methodes roept Filament aan?
Filament kijkt automatisch naar de geregistreerde policy van het model achter een resource. De resource-docs noemen deze methodes:
viewAny()verbergt de resource uit de navigatie en blokkeert alle pagina'sview(),create(),update(),delete()voor losse recordsdeleteAny()voor bulk deleteforceDelete(),forceDeleteAny(),restore(),restoreAny()voor soft deletesreorder()voor het herordenen van tabelrijen
Let op deleteAny(). Filament gebruikt die voor bulk acties omdat per record delete() checken niet performant is, aldus de docs. Heb je regels per record, zoals "alleen eigen orders verwijderen"? Zet dan ->authorizeIndividualRecords() op je bulk action. Anders mag iemand met deleteAny alles in de selectie weggooien.
De grootste valkuil: geen policy = alles mag
Dit is de bug die we het vaakst tegenkomen in audits. De Filament-docs zijn er duidelijk over: bestaat de policy of policy-methode niet, dan krijgt de user toegang. Filament gaat ervan uit dat je autorisatie nog niet hebt ingericht.
Nieuwe resource toegevoegd, policy vergeten? Dan kan elke user met panel-toegang erin. De fix is één regel in je panel provider:
return $panel
// ...
->strictAuthorization();
Met strictAuthorization() gooit Filament een exception als een policy of methode ontbreekt. Je merkt het in development, niet in productie. Wij zetten dit standaard aan op elk nieuw project.
Policies dekken niet alles
De security-pagina van Filament zegt het letterlijk: automatische autorisatie geldt alleen voor de ingebouwde resource-operaties. Alles wat je zelf bouwt, autoriseer je zelf. Dat zijn:
- Custom actions (een "Exporteer"- of "Stuur naar ERP"-knop)
- Custom pages en Livewire-componenten
- Inline editable kolommen zoals
ToggleColumnenSelectColumn: die checken géén policy, alleen hundisabled()-state - Je API-routes buiten Filament
Een custom action beveilig je met ->authorize('update'). Filament zoekt dan de policy van het model erbij. Geeft je policy een Response::deny('reden') terug, dan toont ->authorizationTooltip() die reden bij een uitgeschakelde knop. Dat werkt sinds v4 goed met Laravel's policy responses.
Meer van dit soort gaten staan in onze Filament beveiligingschecklist.
Wanneer heb je Spatie laravel-permission nodig?
Zodra rollen door een beheerder moeten worden aangepast zonder deploy. Een policy met return $user->is_admin; werkt prima voor twee rollen. Bij vijf rollen en wisselende rechten wordt het een if-kerstboom.
Spatie laravel-permission slaat rollen en permissions op in de database. Je voegt de HasRoles-trait toe aan je User-model en checkt met $user->can('Update:Order') of $user->hasRole('magazijn'). De laatste versie is 8.3.0 (juli 2026, volgens Packagist), met PHP 8.3+ en Laravel 12 of 13 als vereiste.
De eerlijke versie: voor veel interne tools is Spatie overkill. Heb je drie vaste rollen die nooit veranderen? Dan is een enum-kolom op users plus handgeschreven policies simpeler, sneller en makkelijker te testen.
| Situatie | Advies |
|---|---|
| 1-3 vaste rollen, wijzigt zelden | Enum op User + policies, geen package |
| Rollen wijzigen, beheerder moet dit zelf doen | Spatie + Shield |
| Rechten per klant/tenant verschillend | Spatie met teams + Shield tenancy-support |
| Record-niveau regels (eigen data) | Altijd in de policy, ongeacht package |
Wat is Filament Shield en welke versie heb je nodig?
Filament Shield (bezhansalleh/filament-shield) is een plugin die Spatie-permissions koppelt aan Filament. Het genereert policies en permissions voor je resources, pages en widgets, en levert een Role-resource om rollen via de UI te beheren.
Versiecompatibiliteit volgens de README op GitHub:
| Shield | Filament |
|---|---|
| 2.x | 2.x |
| 3.x | 3.x |
| 4.x | 4.x en 5.x |
De huidige release is 4.3.1 (juli 2026). Die vraagt PHP 8.2+, Laravel 11.28 of hoger, en ondersteunt spatie/laravel-permission 6, 7 en 8, aldus Packagist.
Shield installeren in 5 stappen
composer require bezhansalleh/filament-shieldHasRoles-trait op je User-model zettenphp artisan shield:setupdraaien (interactief, publiceert config en migraties)- Plugin registreren in je panel provider:
->plugin(\BezhanSalleh\FilamentShield\FilamentShieldPlugin::make()) php artisan shield:generate --allvoor policies en permissions, daarnaphp artisan shield:super-adminvoor de eerste beheerder
Pages en widgets beveilig je door de trait HasPageShield of HasWidgetShield toe te voegen. Zonder die trait staan ze open voor iedereen in het panel.
Hoe heten de permissions in Shield 4?
Standaard PascalCase met een dubbele punt: ViewAny:User, Create:Order, Delete:Product. Dat is instelbaar via permissions.case en permissions.separator in config/filament-shield.php.
Upgrade je van Shield 3, dan is dit een aandachtspunt. Daar was snake_case (view_any_user) gebruikelijk. Bestaande rollen in je database verwijzen naar de oude namen. Kies bewust: zet de config op je oude formaat, of migreer de permission-namen in een data-migratie.
Permissions die niet bij een resource horen, zoals Export:Order of Impersonate:User, zet je onder custom_permissions in de config.
super_admin en panel_user
Shield maakt standaard twee rollen aan: super_admin en panel_user. De super admin mag alles. Dat loopt via een gate-intercept (intercept_gate => 'before' in de config), dus policies worden voor die rol overgeslagen.
Dat is handig. Maar het betekent ook dat je met een super admin-account nooit ziet of je policies kloppen. Test altijd met een gewone rol.
Hoe regel je rechten op veldniveau?
Filament heeft geen apart systeem voor veldrechten. Je gebruikt visible(), hidden() en disabled() met een closure die de permission checkt.
use Filament\Forms\Components\TextInput;
TextInput::make('inkoopprijs')
->visible(fn (): bool => auth()->user()->can('ViewCost:Product'));
TextInput::make('korting_percentage')
->disabled(fn (): bool => ! auth()->user()->can('EditDiscount:Order'));
Twee dingen om te weten, allebei uit de Filament-docs:
- Een
disabled()-veld wordt niet opgeslagen. Wil je het wel opslaan, dan kan->saved(). Maar de docs waarschuwen: een handige user kan de waarde dan via Livewire's JavaScript manipuleren. - Filament stuurt alle model-attributen die niet in
$hiddenstaan naar de browser via Livewire. Verberg je een veld in het formulier, dan zit de waarde mogelijk nog in de payload. Echt gevoelige data (inkoopprijzen, marges, BSN) hoort in$hiddenop het model of buiten het record.
Voor tabellen werkt hetzelfde: TextColumn::make('marge')->visible(...). Inline editable kolommen beveilig je via disabled(), want daar checkt Filament geen policy.
Best practices die we zelf hanteren
Dit is wat we in de praktijk op elk Filament-project toepassen:
strictAuthorization()aan vanaf dag één. Vergeten policies vind je dan in development.- Record-regels in de policy, niet in de query. Filter je met
getEloquentQuery()op eigen records, zet dezelfde check ook inview()enupdate(). Een query-filter is geen autorisatie. - Eén bron van waarheid. Gebruik je Shield, laat dan geen handgeschreven
$user->is_admin-checks rondslingeren. Twee systemen lopen uit elkaar. - Shield-policies na generatie reviewen. Shield overschrijft bestaande policy-bestanden bij opnieuw genereren. Heb je ze aangepast, gebruik dan
--ignore-existing-policies. - Permission-cache niet vergeten. Spatie cachet permissions. Wijzig je rollen via een seeder of direct in de database, draai dan
php artisan permission:cache-reset. - Multi-tenancy apart doordenken. Rechten per tenant vergen Spatie teams of een eigen opzet. Zie Filament multi-tenancy voor SaaS.
- Autorisatie testen per rol. Filament heeft testhelpers om per rol te checken of pagina's en acties wel of niet bereikbaar zijn. Hoe dat met Pest werkt, lees je in Filament testen met Pest.
Shield of zelf bouwen?
Shield is de juiste keuze als een niet-developer rollen moet kunnen beheren. Zelf bouwen is beter bij weinig, stabiele rollen of zware record-level logica.
| Criterium | Shield | Eigen policies |
|---|---|---|
| Opzettijd | Snel, generators doen het meeste | Langer, maar je schrijft alleen wat je nodig hebt |
| Rollen via UI beheren | Ja, ingebouwde Role-resource | Zelf bouwen |
| Record-level regels | Handmatig toevoegen in gegenereerde policy | Natuurlijk onderdeel |
| Upgrade-risico | Afhankelijk van plugin-maintainer | Alleen Laravel/Filament zelf |
| Overzicht in code | Veel gegenereerde boilerplate | Compact, expliciet |
Onze vuistregel: een interne backoffice met drie rollen bouwen we zonder Shield. Een platform waar de klant zelf medewerkers en rollen aanmaakt, krijgt Shield. Vergelijk je nog admin-frameworks? Lees dan Laravel Nova vs Filament.
Samengevat
canAccessPanel()is de voordeur, per panel in te stellen. Zonder implementatie heeft in productie niemand toegang.- Filament gebruikt Laravel policies. Ontbreekt een policy, dan mag alles, tenzij je
strictAuthorization()aanzet. - Custom actions, pages en inline editable kolommen autoriseer je zelf.
- Spatie laravel-permission is nuttig zodra rollen dynamisch zijn. Bij weinig vaste rollen is het overkill.
- Shield 4.x werkt met Filament 4 en 5 en Spatie 6 tot en met 8. Let op de nieuwe permission-namen bij een upgrade vanaf Shield 3.
- Veldrechten regel je met
visible()endisabled(), maar gevoelige data hoort ook in$hiddenop het model.
Veelgestelde vragen
Werkt Filament Shield met Filament 5?
Ja. Shield 4.x ondersteunt zowel Filament 4 als Filament 5, volgens de compatibiliteitstabel in de README. Dat klopt ook logisch: Filament 5 heeft dezelfde API als v4 en voegt alleen Livewire 4-support toe. Gebruik Shield 3.x alleen nog voor Filament 3-projecten.
Wat gebeurt er als ik geen policy maak voor een resource?
Dan geeft Filament elke user met panel-toegang volledige toegang tot die resource. Filament neemt aan dat je autorisatie nog niet hebt ingericht. Zet strictAuthorization() op je panel om in plaats daarvan een exception te krijgen.
Heb ik Spatie laravel-permission nodig voor Filament?
Nee. Filament werkt met elke Laravel policy. Spatie is alleen nuttig als rollen en rechten in de database moeten staan en door beheerders aanpasbaar moeten zijn. Bij een paar vaste rollen is een enum-kolom met eigen policies eenvoudiger.
Hoe verberg ik een veld voor bepaalde rollen?
Gebruik ->visible(fn () => auth()->user()->can('...')) of ->hidden() op het veld of de kolom. Voor alleen-lezen gebruik je ->disabled(); dat veld wordt dan ook niet opgeslagen. Echt gevoelige attributen zet je daarnaast in $hidden op het model, omdat Livewire anders model-data naar de browser kan sturen.
Waarom ziet mijn super admin alles, ook zonder permissions?
Shield laat de super_admin-rol standaard via een gate-intercept alle checks passeren. Policies worden voor die rol dus niet uitgevoerd. Test je rechtenmodel daarom altijd met een gewone rol, niet met je eigen beheerdersaccount.
Kan ik per panel andere rechten instellen?
Voor panel-toegang wel: canAccessPanel() krijgt het panel mee, dus je checkt per panel-ID. Andere policies per panel voor hetzelfde model zijn lastiger. Volgens de Shield-docs is dat conditionele registratie die je zelf moet bouwen.
Twijfel je of het rechtenmodel van je Filament-applicatie waterdicht is? Neem contact op, dan kijken we er samen naar.

Geschreven door Ruthger Idema
15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.
Meer over ons team →