PHPackages                             bezhansalleh/filament-shield - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. bezhansalleh/filament-shield

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

bezhansalleh/filament-shield
============================

Filament support for `spatie/laravel-permission`.

4.3.1(3w ago)2.8k4.3M↑14.4%304[3 PRs](https://github.com/bezhanSalleh/filament-shield/pulls)20MITPHPPHP ^8.2|^8.3CI passing

Since Jan 3Pushed 3w ago18 watchersCompare

[ Source](https://github.com/bezhanSalleh/filament-shield)[ Packagist](https://packagist.org/packages/bezhansalleh/filament-shield)[ Docs](https://github.com/bezhansalleh/filament-shield)[ GitHub Sponsors](https://github.com/bezhanSalleh)[ RSS](/packages/bezhansalleh-filament-shield/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (10)Dependencies (66)Versions (112)Used By (20)

[![filament-shield-art](https://repository-images.githubusercontent.com/443775406/08a6c1ef-c8cc-4085-afb3-fb916ce6d3c6)](https://github.com/bezhansalleh/filament-shield) [ ![FILAMENT 4.x](https://camo.githubusercontent.com/13befbe0cffd101b2484ce1cdd985dabcddcbf1176d17a675674d53af99cfdbc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f46494c414d454e542d342e782d4542423330343f7374796c653d666f722d7468652d6261646765) ](https://filamentphp.com/docs/4.x/panels/installation) [ ![FILAMENT 5.x](https://camo.githubusercontent.com/3ec960aa304b803da9f1192f01e82da976a3d220ea85931fec930003e0532c77/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f46494c414d454e542d352e782d4542423330343f7374796c653d666f722d7468652d6261646765) ](https://filamentphp.com/docs/5.x/panels/installation) [ ![Packagist](https://camo.githubusercontent.com/126fd175ebfd3bafa03281545d08de7b31567dea23450ae51545f1059b450119/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f62657a68616e73616c6c65682f66696c616d656e742d736869656c642e7376673f7374796c653d666f722d7468652d6261646765266c6f676f3d7061636b6167697374) ](https://packagist.org/packages/bezhansalleh/filament-shield) [ ![Tests Passing](https://camo.githubusercontent.com/8682b7e5b6a5d23ff0b7c5c1bf7b821b0c5bb6c1ec8755566003b3dbd1cedd24/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f62657a68616e73616c6c65682f66696c616d656e742d736869656c642f72756e2d74657374732e796d6c3f7374796c653d666f722d7468652d6261646765266c6f676f3d676974687562266c6162656c3d7465737473) ](https://github.com/bezhansalleh/filament-shield/actions?query=workflow%3Arun-tests+branch%3Amain) [ ![Code Style Passing](https://camo.githubusercontent.com/fe92ca3e6cbf868290f2655454d6a51d6b1fd2dd9cc65f137acb50aff3307842/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f62657a68616e73616c6c65682f66696c616d656e742d736869656c642f6c61726176656c2d70696e742e796d6c3f7374796c653d666f722d7468652d6261646765266c6f676f3d676974687562266c6162656c3d636f64652532307374796c65) ](https://github.com/bezhansalleh/filament-shield/actions?query=workflow%3A)[ ![Downloads](https://camo.githubusercontent.com/5f5332676140ec8b3526c6bf3f8baa4322ecef1117f53583723fc40895ee5797/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f62657a68616e73616c6c65682f66696c616d656e742d736869656c642e7376673f7374796c653d666f722d7468652d6261646765) ](https://packagist.org/packages/bezhansalleh/filament-shield)

Shield
======

[](#shield)

The easiest and most intuitive way to add access management to your Filament panels.

Important

This iteration is a complete rewrite from versions 3.x and 4.x-beta and is not backward compatible. Please refer to the [Upgrade](#upgrade) section on how to proceed.

Features
--------

[](#features)

- 🛡️ **Complete Authorization Management**
    - 📦 Resource Permissions
    - 📄 Page Permissions
    - 🧩 Widget Permissions
- 🛠️ **Custom (ad-hoc) permissions**
- 🤖 **Automatic Policy Generation**
    - 📜 Default Policy methods for Filament Resources
    - 🏷️ Per Resource Policy definition
    - 🔗 Third-party resource policy &amp; permission generation
- 👑 **Super admin role or gate interception**
- 👤 **Optional baseline panel user role**
- 🔄 **Multi-tenancy Support**
- 🔍 **Entity discovery** (across all panels if enabled)
- 🌐 **Localized permission &amp; entity labels**
- 🌱 **Seeder generation** (roles + direct permissions)
- 🎨 **Intuitive UI**
    - 🖌️ Publish &amp; customize the built-in resource
- ⚡ **Fine-grained CLI tooling** with safe prohibiting

Compatibility
-------------

[](#compatibility)

Package VersionFilament Version[2.x](https://github.com/bezhanSalleh/filament-shield/tree/2.x)2.x[3.x](https://github.com/bezhanSalleh/filament-shield/tree/3.x)3.x**4.x**4.x &amp; 5.x

**Table of Contents**- [Installation](#installation)
    - [1. Install Package](#1-install-package)
    - [2. Configure Auth Provider](#2-configure-auth-provider)
    - [3. Setup Shield](#3-setup-shield)
- [Usage &amp; Configuration](#usage--configuration)
    - [Permissions](#permissions)
        - [Configuration](#configuration)
        - [Case](#case)
        - [Customize permission key composition](#customize-permission-key-composition)
    - [Policies](#policies)
        - [Configuration](#configuration-1)
        - [Policy Placement](#policy-placement)
        - [Skipping Provided Policies](#skipping-provided-policies)
        - [Methods](#methods)
        - [Merge](#merge)
        - [Single Parameter Methods](#single-parameter-methods)
        - [Policy Enforcement](#policy-enforcement)
    - [Resources](#resources)
        - [Configuration](#configuration-2)
        - [Subject](#subject)
        - [Manage](#manage)
        - [Exclude](#exclude)
    - [Pages &amp; Widgets](#pages--widgets)
        - [Configuration](#configuration-3)
        - [Options](#options)
        - [Permission Enforcement](#permission-enforcement)
    - [Custom Permissions](#custom-permissions)
        - [Formatting](#formatting)
        - [Externally Managed Permissions](#externally-managed-permissions)
    - [Retrieving the Permission Catalogue](#retrieving-the-permission-catalogue)
    - [Users (Assigning Roles to Users)](#users-assigning-roles-to-users)
    - [Shield Plugin &amp; Resource](#shield-plugin--resource)
        - [Navigation](#navigation)
        - [Labels](#labels)
        - [Global Search](#global-search)
        - [Parent Resource](#parent-resource)
        - [Tenancy](#tenancy)
        - [Layout Customization](#layout-customization)
    - [Commands](#commands)
        - [Prohibited Commands](#prohibited-commands)
        - [Core Commands](#core-commands)
        - [Generate Command Options (recap)](#generate-command-options-recap)
        - [Seeder Command Options (recap)](#seeder-command-options-recap)
        - [Custom Super Admin Creation](#custom-super-admin-creation)
    - [Localization](#localization)
        - [Configuration](#configuration-4)
        - [How It Works](#how-it-works)
        - [Generating Translation Files](#generating-translation-files)
        - [Translation Keys](#translation-keys)
        - [Default Package Translations](#default-package-translations)
- [Upgrade](#upgrade)
- [Translations](#translations)
- [Testing](#testing)
- [Changelog](#changelog)
- [Contributing](#contributing)
- [Security Vulnerabilities](#security-vulnerabilities)
- [Credits](#credits)
- [License](#license)

Installation
============

[](#installation)

1. Install Package
------------------

[](#1-install-package)

```
composer require bezhansalleh/filament-shield
```

2. Configure Auth Provider
--------------------------

[](#2-configure-auth-provider)

1. Publish the config and set your auth provider model. ```
    php artisan vendor:publish --tag="filament-shield-config"
    ```

    ```
    // config/filament-shield.php
    return [
        // ...
        'auth_provider_model' => 'App\\Models\\User',
        // ...
    ];
    ```
2. Add the `HasRoles` trait to your auth provider model: ```
    use Spatie\Permission\Traits\HasRoles;

    class User extends Authenticatable
    {
        use HasRoles;
    }
    ```

3. Setup Shield
---------------

[](#3-setup-shield)

Run the setup command (it is interactive and smart):

```
php artisan shield:setup
```

Usage &amp; Configuration
=========================

[](#usage--configuration)

The package comes with a sensible default configuration that should work for most applications. You can customize the configuration by modifying it to fit your needs. The following sections explain the various configuration options available.

Permissions
-----------

[](#permissions)

You can customize how permission keys are generated to match your preferred naming conventions and organizational standards. Shield uses these settings from the `filament-shield.php` **config** file when creating permission names from your `{Resources|Pages|Widgets}`.

### Configuration

[](#configuration)

```
'permissions' => [
    'separator' => ':',
    'case' => 'pascal',
    'generate' => true,
    'format_custom_permission_keys' => true,
],
```

> **Separator &amp; case compatibility:** The separator must not conflict with the case format's own delimiter. Using `_` with `snake`/`lower_snake`/`upper_snake`, or `-` with `kebab`, will throw an `InvalidArgumentException` since it would be impossible to distinguish the affix from the subject in the resulting permission key.

### Case

[](#case)

Shield formats permission keys using the specified case style. The available options are:

- `camel`
- `kebab`
- `snake`
- `pascal` (default)
- `upper_snake`

### Customize permission key composition

[](#customize-permission-key-composition)

You can customize how permission keys are generated by providing your own callback to `buildPermissionKeyUsing` in your `AppServiceProvider`'s `boot()` method. The callback receives the following parameters:

- `string $entity`: The FQCN of the entity for resources/pages/widgets, or `'custom'` for custom permissions.
- `?string $affix`: The action or method name (e.g., 'viewAny', 'create'). `null` for custom permissions.
- `string $subject`: The subject or resource name (e.g., 'Post', 'Dashboard'). For custom permissions, this is the raw permission key as defined in config.
- `string $case`: The case format specified in the config (e.g., 'pascal').
- `string $separator`: The separator specified in the config (e.g., ':').

Return a `string` to use as the permission key, or `null` to fall back to the default permission key builder. This allows you to selectively override specific entity types while keeping the default behavior for others:

- Now let's consider an example where we want to handle `Resource` entities that handle the same `Model` or `Models` with the same name but with different namespaces and directory structures. The Filament [Demo](https://github.com/filamentphp/demo) has two resources with the same name that handle two different models:

    - `App\Filament\Resources\Blog\Categories\CategoryResource` that handles `App\Models\Blog\Category`
    - `App\Filament\Resources\Shop\Categories\CategoryResource` that handles `App\Models\Shop\Category`

    By default Shield will generate the same permission keys for both resources which can cause conflicts. To avoid this we can customize the permission key composition to include the navigation group of the resource as part of the permission key. Here's how you can do it:

    ```
    use BezhanSalleh\FilamentShield\Facades\FilamentShield;
    use Filament\Resources\Resource;

    FilamentShield::buildPermissionKeyUsing(
        function (string $entity, string $affix, string $subject, string $case, string $separator) {
          if (is_subclass_of($entity, Resource::class) && in_array(
                needle: $entity,
                haystack: [
                    'App\Filament\Resources\Blog\Categories\CategoryResource',
                    'App\Filament\Resources\Shop\Categories\CategoryResource'
                ],
                strict: true
          )) {
              $subject = str($subject)
                  ->prepend($resource::getNavigationGroup())
                  ->trim()
                  ->toString();
          }

          return FilamentShield::defaultPermissionKeyBuilder(
              affix: $affix,
              separator: $separator,
              subject: $subject,
              case: $case
          );
      }
    );
    ```

    Now when you run the `shield:generate` command, it will generate distinct permission keys for each `CategoryResource` based on their navigation groups:

    - For `Blog`'s `CategoryResource` since its navigation group is `Blog`:
        - `ViewAny:BlogCategories`
        - `View:BlogCategories`
        - `Create:BlogCategories`
        - `Update:BlogCategories`
        - `Delete:BlogCategories`
    - For `Shop`'s `CategoryResource` since it uses a cluster and its navigation group is blank, so it will just use the resource `subject` configured in the config `filament-shield.resources.subject` which is `model` by default:
        - `ViewAny:Categories`
        - `View:Categories`
        - `Create:Categories`
        - `Update:Categories`
        - `Delete:Categories`

This approach ensures that each resource has a unique set of permission keys, preventing any conflicts and allowing for more granular access control. You can of course extract the logic to a separate class or function if it gets too complex, but this should give you a good starting point.

- **Returning `null` for default fallback:** You can return `null` from the closure to let the default builder handle specific entity types. This is useful when you only want to customize certain entities (e.g., custom permissions from Keycloak) while letting everything else use the standard formatting:

    ```
    use BezhanSalleh\FilamentShield\Facades\FilamentShield;

    FilamentShield::buildPermissionKeyUsing(
        function (string $entity, ?string $affix, string $subject, string $case, string $separator) {
            // Custom permissions from external source — use as-is
            if ($entity === 'custom') {
                return $subject;
            }

            // Everything else uses the default builder
            return null;
        }
    );
    ```

Policies
--------

[](#policies)

Shield automatically generates policies for your Resources' Models.

### Configuration

[](#configuration-1)

```
'policies' => [
    'path' => app_path('Policies'),
    'merge' => true,
    'generate' => true,
    'methods' => [
        'viewAny', 'view', 'create', 'update', 'delete', 'deleteAny', 'restore',
        'forceDelete', 'forceDeleteAny', 'restoreAny', 'replicate', 'reorder',
    ],
    'single_parameter_methods' => [
        'viewAny',
        'create',
        'deleteAny',
        'forceDeleteAny',
        'restoreAny',
        'reorder',
    ],
],
```

### Policy Placement

[](#policy-placement)

Shield writes each policy where it belongs for the model that owns it; how policies are resolved at runtime stays in your hands. The rule is applied per model:

1. **Models under `app/Models`** — the policy goes into `policies.path`, keeping any nesting (`app/Models/Blog/Post.php` → `app/Policies/Blog/PostPolicy.php`).
2. **Vendor models** — the policy goes flat into `policies.path`. Shield never writes inside `vendor/`, since Composer wipes it.
3. **Models in any other `Models` directory** (modules, plugins, DDD domains, panel-organized trees) — the policy goes into a sibling `Policies` directory beside the model, exactly where Laravel's policy discovery looks.
4. **Models outside any `Models` directory** (legacy `app/User.php` layouts) — the policy goes flat into `policies.path`.

Model locationGenerated policyFound by Laravel's discovery?Action needed`app/Models/Post.php` (default `policies.path`)`App\Policies\PostPolicy`Yesnone`app/Models/Blog/Post.php``App\Policies\Blog\PostPolicy`No`enforcePolicies()` or register`app/Models/Post.php` (custom `policies.path`)e.g. `App\Filament\Policies\PostPolicy`No`enforcePolicies()` or register`app/Filament/Admin/Models/Post.php``App\Filament\Admin\Policies\PostPolicy`Yesnone`modules/Blog/src/Models/Post.php``Modules\Blog\Policies\PostPolicy`Yesnone`app/Domain/Users/Models/Post.php``App\Domain\Users\Policies\PostPolicy`Yesnonevendor model, no bundled policy`App\Policies\PostPolicy`No`enforcePolicies()` or register (`register_role_policy` already covers Shield's `Role`)`app/User.php` (no `Models` directory)`App\Policies\UserPolicy`YesnoneBecause the rule is per-model, mixed layouts work with zero configuration: a default `app/Models` tree, an `app-modules/` directory, and vendor models can coexist in one app. Grouping models **inside** `app/Models` (e.g. `app/Models/Shared`, `app/Models/Admin`) mirrors the grouping into your policy tree under `policies.path`; grouping them **outside** it (e.g. `app/Filament/Admin/Models`) yields sibling placement that Laravel discovers on its own — the directory choice selects the trade-off.

### Skipping Provided Policies

[](#skipping-provided-policies)

When a model's policy already resolves to something other than the policy Shield would generate — for example a policy bundled with an installed plugin, or one you registered yourself — `shield:generate` skips that model and reports which policy provides it. Permissions are still generated.

Ownership is decided structurally, with two symmetric recipes and no flags:

- **Opting a model out** — put your policy anywhere you like, register it with `Gate::policy()`, and delete the file Shield generated. Shield treats it like a plugin-provided policy and backs off that model for good, while still generating its permissions.
- **Taking over a provided policy** — create a policy class at Shield's conventional location for the model (see the placement table above), for example with `php artisan make:policy`. Once that class exists, the next `shield:generate` fills it and maintains it from then on. Register it with `Gate::policy()` so it wins over the plugin's — explicit registrations beat discovered ones.

The `--ignore-existing-policies` flag is an unrelated axis: it prevents rewriting any policy file that already exists, protecting manual edits. The skip rule decides whether a model is Shield's to generate for; the flag then decides whether an existing file may be rewritten.

One caveat: the check runs in the console, so registrations that only happen conditionally at runtime may not be visible while generating. The worst case is an extra generated file that never resolves — Shield itself never registers anything without being asked.

### Methods

[](#methods)

Each policy includes methods defined in the `policies.methods` config. You can customize this list to fit your application's needs. Since Filament Resources typically use a standard set of methods, the default configuration should suffice for most applications. If you have specific resources that require additional methods, you can easily add them to the list. However, it would be best to only include methods that are commonly used across your resources and define any resource-specific methods in the `resources.manage` config section. This approach keeps your policies clean and relevant to your application's requirements.

### Merge

[](#merge)

When `policies.merge` is set to `true`, Shield will combine the global methods defined in `policies.methods` with any resource-specific methods you define in `resources.manage`. This ensures that each resource's policy includes both the standard methods and any additional ones you need for that particular resource.

### Single Parameter Methods

[](#single-parameter-methods)

Some policy methods only require the user instance as a parameter (e.g., `viewAny`, `create`). These are defined in `policies.single_parameter_methods`. Shield will generate these methods accordingly in the policies. When you add new methods or resource-specific methods, ensure to update this list if they also only require the user instance. This helps maintain consistency and clarity in your policy definitions.

### Policy Enforcement

[](#policy-enforcement)

Sibling-placed policies and the default flat `App\Policies` are found by Laravel's policy discovery on their own. The placements marked "No" in the table above — nested under `policies.path`, a custom `policies.path`, and centralized vendor-model policies — are invisible to it and need registration. The simplest way is Shield's opt-in enforcement hook, in a service provider's `boot()` method:

```
use BezhanSalleh\FilamentShield\Facades\FilamentShield;

FilamentShield::enforcePolicies();
```

At `Filament::serving` time, this registers the Shield-generated policy for each of your resources' models via `Gate::policy()`. It plays by strict rules:

- The condition is evaluated lazily on each request, so a closure can gate enforcement per panel or per tenant: `FilamentShield::enforcePolicies(fn (): bool => Filament::getCurrentPanel()?->getId() === 'admin')`.
- Models listed in `$except` are left alone: `FilamentShield::enforcePolicies(except: [Post::class])`.
- Existing explicit `Gate::policy()` registrations — yours or a plugin's — always win, regardless of boot order.
- Only Shield-generated policy classes that actually exist are registered; your custom-located policies are never touched, and nothing is ever unregistered or overridden.

For full manual control, register policies yourself:

```
Gate::policy(Awcodes\Curator\Models\Media::class, App\Policies\MediaPolicy::class);
```

**Tip** Alternatively, you can teach Laravel's discovery your convention with `Gate::guessPolicyNamesUsing()`. If you use a custom `policies.path`, adapt the callback to your configured namespace, since discovery never looks inside a custom path on its own:

```
use Illuminate\Support\Facades\Gate;

Gate::guessPolicyNamesUsing(function (string $modelClass) {
    return str_replace('Models', 'Policies', $modelClass) . 'Policy';
});
```

One boundary to be aware of: resolving *different* policies per panel for the same model is conditional resolution, which belongs in your own registration logic (a conditioned `enforcePolicies()` closure only gates whether Shield's policies are enforced — it does not swap policies per panel).

Resources
---------

[](#resources)

Shield derives resource permission keys from configured policy methods. Since Filament `Resources`' authorization is handled via policies, generated permissions align with policy methods.

### Configuration

[](#configuration-2)

```
'resources' => [
    'subject' => 'model',
    'manage' => [
        \BezhanSalleh\FilamentShield\Resources\Roles\RoleResource::class => [
            'viewAny',
            'view',
            'create',
            'update',
            'delete',
        ],
    ],
    'exclude' => [
        //
    ],
],
```

### Subject

[](#subject)

You can customize the subject used for resource permissions by setting the `subject` key in the `resources` configuration. The subject can be set to either `class` or `model` (default is `model`).

### Manage

[](#manage)

You can define resource-specific policy methods in the `resources.manage` configuration. This allows you to tailor the permissions for individual resources (in-app or third-party) based on their unique requirements. When you specify methods here, Shield will generate permissions for these methods in addition to the global methods defined in `policies.methods`, provided that `policies.merge` is set to `true`. This ensures that each resource has a comprehensive set of permissions that reflect both standard and resource-specific actions.

### Exclude

[](#exclude)

You can exclude specific resources from permission generation by listing them in the `resources.exclude` configuration. This is useful for resources that should always be accessible or do not require permission checks. When a resource is excluded, Shield will skip generating permissions and policy for it.

Pages &amp; Widgets
-------------------

[](#pages--widgets)

Both **pages** and **widgets** in Filament follow a similar permission model. By default, they require **view** permissions. You can customize their behavior in the configuration, including subject, prefix, exclusions, and enforcement traits.

### Configuration

[](#configuration-3)

**Pages**

```
'pages' => [
    'subject' => 'class',
    'prefix' => 'view',
    'exclude' => [
        \Filament\Pages\Dashboard::class,
    ],
],
```

**Widgets**

```
'widgets' => [
    'subject' => 'class',
    'prefix' => 'view',
    'exclude' => [
        \Filament\Widgets\AccountWidget::class,
        \Filament\Widgets\FilamentInfoWidget::class,
    ],
],
```

### Options

[](#options)

OptionDescription**Subject**Determines how the permission subject is generated.
• `class` → Uses the class name (default).
• `model` → Uses the model name (if the entity has a `static getModel()` method).**Prefix**Prepended to permission keys for distinction.
• Example for Pages: `Page:IconLibrary`
• Example for Widgets: `Widget:IncomeWidget`.**Exclude**Entities listed here will be skipped during permission generation.
Useful for always-accessible entities like dashboards, account widgets, or system info.### Permission Enforcement

[](#permission-enforcement)

Use the appropriate **Shield trait** to automatically enforce permissions. When applied, these traits ensure:

- Navigation or rendering is **hidden** if the user lacks permission.
- Access to the page/widget is **restricted**.

**Pages**

```
