Filament support for `spatie/laravel-permission`.
bezhansalleh/filament-shield is a Laravel package for filament support for `spatie/laravel-permission`..
It currently has 2.817 GitHub stars and 4.533.832 downloads on Packagist (latest version 4.3.1).
Install it with composer require bezhansalleh/filament-shield.
Discover more Laravel packages by bezhansalleh
or browse all Laravel packages to compare alternatives.
Last updated
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 section on how to proceed.
| Package Version | Filament Version | |-----------------|------------------| | 2.x | 2.x | | 3.x | 3.x | | 4.x | 4.x & 5.x |
composer require bezhansalleh/filament-shield
php artisan vendor:publish --tag="filament-shield-config"
// config/filament-shield.php
return [
// ...
'auth_provider_model' => 'App\\Models\\User',
// ...
];
HasRoles trait to your auth provider model:
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use HasRoles;
}
Run the setup command (it is interactive and smart):
php artisan shield:setup
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.
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}.
'permissions' => [
'separator' => ':',
'case' => 'pascal',
'generate' => true,
'format_custom_permission_keys' => true,
],
Separator & case compatibility: The separator must not conflict with the case format's own delimiter. Using
_withsnake/lower_snake/upper_snake, or-withkebab, will throw anInvalidArgumentExceptionsince it would be impossible to distinguish the affix from the subject in the resulting permission key.
Shield formats permission keys using the specified case style. The available options are:
camelkebabsnakepascal (default)upper_snakeYou 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 has two resources with the same name that handle two different models:
App\Filament\Resources\Blog\Categories\CategoryResource that handles App\Models\Blog\CategoryApp\Filament\Resources\Shop\Categories\CategoryResource that handles App\Models\Shop\CategoryBy 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:
Blog's CategoryResource since its navigation group is Blog:
ViewAny:BlogCategoriesView:BlogCategoriesCreate:BlogCategoriesUpdate:BlogCategoriesDelete:BlogCategoriesShop'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:CategoriesView:CategoriesCreate:CategoriesUpdate:CategoriesDelete:CategoriesThis 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;
}
);
Shield automatically generates policies for your Resources' Models.
'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',
],
],
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:
app/Models β the policy goes into policies.path, keeping any nesting (app/Models/Blog/Post.php β app/Policies/Blog/PostPolicy.php).policies.path. Shield never writes inside vendor/, since Composer wipes it.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.Models directory (legacy app/User.php layouts) β the policy goes flat into policies.path.| Model location | Generated policy | Found by Laravel's discovery? | Action needed |
|---|---|---|---|
| app/Models/Post.php (default policies.path) | App\Policies\PostPolicy | Yes | none |
| 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 | Yes | none |
| modules/Blog/src/Models/Post.php | Modules\Blog\Policies\PostPolicy | Yes | none |
| app/Domain/Users/Models/Post.php | App\Domain\Users\Policies\PostPolicy | Yes | none |
| vendor 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 | Yes | none |
Because 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.
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:
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.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.
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.
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.
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.
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:
FilamentShield::enforcePolicies(fn (): bool => Filament::getCurrentPanel()?->getId() === 'admin').$except are left alone: FilamentShield::enforcePolicies(except: [Post::class]).Gate::policy() registrations β yours or a plugin's β always win, regardless of boot order.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).
Shield derives resource permission keys from configured policy methods. Since Filament Resources' authorization is handled via policies, generated permissions align with policy methods.
'resources' => [
'subject' => 'model',
'manage' => [
\BezhanSalleh\FilamentShield\Resources\Roles\RoleResource::class => [
'viewAny',
'view',
'create',
'update',
'delete',
],
],
'exclude' => [
//
],
],
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).
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.
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.
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.
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,
],
],
| Option | Description |
|------------|-------------|
| 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. |
Use the appropriate Shield trait to automatically enforce permissions. When applied, these traits ensure:
Pages
<?php
namespace App\Filament\Pages;
use ...;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
class MyPage extends Page
{
use HasPageShield;
...
}
Widgets
<?php
namespace App\Filament\Widgets;
use ...;
use BezhanSalleh\FilamentShield\Traits\HasWidgetShield;
class IncomeWidget extends LineChartWidget
{
use HasWidgetShield;
...
}
Most of the time you will have some ad-hoc permissions that don't fit into the Resource, Page, or Widget categories, or you might not want a policy method for them. You can define these under custom_permissions in the config:
'custom_permissions' => [
'Impersonate:User' => 'Impersonate User',
'Export:Order' => 'Export Orders',
],
They appear in the Role Resource's Custom Permissions tab when enabled.
To enable the tab, set shield_resource.tabs.custom_permissions to true in the config.
By default, custom permission keys are formatted according to the configured case setting. When a custom permission contains the configured separator, each segment is formatted independently. For example, with case => 'snake' and separator => ':':
'view system log' β view_system_log'View:SystemLog' β view:system_logShield's formatter is fault-tolerant β it normalizes input regardless of the original format (snake_case, kebab-case, camelCase, PascalCase, UPPER_SNAKE_CASE) before applying the target case conversion.
If your custom permissions come from external sources like Terraform, Keycloak, or other identity providers and must retain their exact key names, set format_custom_permission_keys to false:
'permissions' => [
'separator' => ':',
'case' => 'pascal',
'generate' => true,
'format_custom_permission_keys' => false,
],
With this setting, custom permission keys are stored exactly as defined β no case conversion is applied. This does not affect resource, page, or widget permissions, which are always formatted.
Alternatively, you can use the buildPermissionKeyUsing closure for more granular control β see Customize permission key composition.
Shield can tell you every permission key it manages for a panel β without
touching the database. getEntitiesPermissions() returns a flat, de-duplicated
array of the keys for all four entity types, formatted per your
Permission Builder settings:
policies.methods, merge, and your
resources.manage / exclude configurationprefix and exclude settingsuse BezhanSalleh\FilamentShield\Facades\FilamentShield;
use Filament\Facades\Filament;
Filament::setCurrentPanel('admin');
FilamentShield::getEntitiesPermissions();
// ['ViewAny:User', 'View:User', 'Create:User', ..., 'View:Settings', 'View:IncomeWidget', 'Impersonate:User']
The catalogue is resolved for the current panel β inside a panel request that's already set; in artisan commands, jobs, or tests, set it first as shown above. Results are memoized for the lifetime of the request.
This method only reads β it never creates permission rows. Its typical job is powering your own synchronization, for example a deploy-time seeder that guarantees every permission exists:
use BezhanSalleh\FilamentShield\Facades\FilamentShield;
use Spatie\Permission\Models\Permission;
foreach (FilamentShield::getEntitiesPermissions() as $key) {
Permission::firstOrCreate(['name' => $key, 'guard_name' => 'web']);
}
or the reverse β auditing drift by diffing the catalogue against what's in your database or an externally managed store.
When you need more than the keys β labels, or entity-to-permission grouping β
use the granular getters instead: FilamentShield::getResources(),
getPages(), getWidgets(), and getCustomPermissions() each return their
entities keyed by class, with a permissions array of key => label pairs.
Note: prior to v4.3.1 this method returned page and widget class names in place of their permission keys. If you rely on it, require at least that version.
Shield does not come with a way to assign roles to your users out of the box; however, you can easily assign roles to your users using Filament's Forms Select or CheckboxList component. Inside your users Resource's form, add one of these components and configure them as needed:
// Using Select Component
Forms\Components\Select::make('roles')
->relationship('roles', 'name')
->multiple()
->preload()
->searchable(),
// Using CheckboxList Component
Forms\Components\CheckboxList::make('roles')
->relationship('roles', 'name')
->searchable(),
// Using Select Component
Forms\Components\Select::make('roles')
->relationship('roles', 'name')
->saveRelationshipsUsing(function (Model $record, $state) {
$record->roles()->syncWithPivotValues($state, [config('permission.column_names.team_foreign_key') => getPermissionsTeamId()]);
})
->multiple()
->preload()
->searchable(),
// Using CheckboxList Component
Forms\Components\CheckboxList::make('roles')
->relationship(name: 'roles', titleAttribute: 'name')
->saveRelationshipsUsing(function (Model $record, $state) {
$record->roles()->syncWithPivotValues($state, [config('permission.column_names.team_foreign_key') => getPermissionsTeamId()]);
})
->searchable(),
You can find out more about these components in the Filament Docs
The plugin provides several methods to handle resource-related customizations and overrides without publishing the resource. You can use the plugin as follows:
You may use the following methods to customize the navigation of the RoleResource:
FilamentShieldPlugin::make()
->navigationLabel('Label') // string|Closure|null
->navigationIcon('heroicon-o-home') // string|Closure|null
->activeNavigationIcon('heroicon-s-home') // string|Closure|null
->navigationGroup('Group') // string|Closure|null
->navigationSort(10) // int|Closure|null
->navigationBadge('5') // string|Closure|null
->navigationBadgeColor('success') // string|array|Closure|null
->navigationParentItem('parent.item') // string|Closure|null
->registerNavigation(); // bool|Closure
You may use the following methods to customize the labels of the RoleResource:
FilamentShieldPlugin::make()
->modelLabel('Model') // string|Closure|null
->pluralModelLabel('Models') // string|Closure|null
->recordTitleAttribute('name') // string|Closure|null
->titleCaseModelLabel(false); // bool|Closure
You may use the following methods to customize the global search related functionality of the RoleResource:
FilamentShieldPlugin::make()
->globallySearchable(true) // bool|Closure
->globalSearchResultsLimit(50) // int|Closure
->forceGlobalSearchCaseInsensitive(true) // bool|Closure|null
->splitGlobalSearchTerms(false); // bool|Closure
You may use the following method to set a parent resource for the RoleResource:
FilamentShieldPlugin::make()
->parentResource(ParentResource::class); // string|Closure|null
You may use the following methods to customize the tenancy related functionality of the RoleResource:
FilamentShieldPlugin::make()
->scopeToTenant(true) // bool|Closure
->tenantRelationshipName('organization') // string|Closure|null
->tenantOwnershipRelationshipName('owner'); // string|Closure|null
You can easily customize the Grid, Section and CheckboxList's columns() and columnSpan() without publishing the resource.
FilamentShieldPlugin::make()
->gridColumns([
'default' => 1,
'sm' => 2,
'lg' => 3
])
->sectionColumnSpan(1)
->checkboxListColumns([
'default' => 1,
'sm' => 2,
'lg' => 4,
])
->resourceCheckboxListColumns([
'default' => 1,
'sm' => 2,
]),
You can also make the resource tab to have a simple view like the other tabs by using the following method:
FilamentShieldPlugin::make()
->simpleResourcePermissionView()
When you have localization enabled and setup and you want the permission labels to react to your application's chosen locale/language you can use the following method:
FilamentShieldPlugin::make()
->localizePermissionLabels()
Since almost all Shield commands are destructive and can cause data loss, they can be prohibited by calling the prohibit method of the command as follows in a service provider's boot() method:
use BezhanSalleh\FilamentShield\Facades\FilamentShield;
use BezhanSalleh\FilamentShield\Commands;
public function boot(): void
{
// individually prohibit commands
Commands\GenerateCommand::prohibit($this->app->isProduction());
Commands\InstallCommand::prohibit($this->app->isProduction());
Commands\PublishCommand::prohibit($this->app->isProduction());
Commands\SetupCommand::prohibit($this->app->isProduction());
Commands\SeederCommand::prohibit($this->app->isProduction());
Commands\SuperAdminCommand::prohibit($this->app->isProduction());
// or prohibit the above commands all at once
FilamentShield::prohibitDestructiveCommands($this->app->isProduction());
}
shield:setup [--fresh] [--tenant=] [--force] [--starred]
shield:install {panel} [--tenant]
shield:generate [--all] [--option=] [--resource=] [--page=] [--widget=] [--exclude] [--ignore-existing-policies] [--panel=] [--relationships]
shield:super-admin [--user=] [--panel=] [--tenant=]
shield:seeder [--generate] [--option=permissions_via_roles|direct_permissions] [--force]
shield:publish --panel={panel} [--cluster=] [--nested] [--force]
shield:translation {locale} [--panel=] [--path=]
--all Generate for all discovered entities
--option=policies_and_permissions|policies|permissions|tenant_relationships Override generator mode
--resource=FooResource,BarResource Target resources (class basenames)
--page=Dashboard,Settings Target pages (basenames)
--widget=StatsOverview,SalesChart Target widgets (basenames)
--exclude Treat provided entities as exclusions
--ignore-existing-policies Skip policies whose file already exists, preserving manual edits
--panel=admin Panel ID (required when not interactive)
--relationships Generate tenancy relationships (panel must have tenancy)
--generate Generate seeder file
--option=permissions_via_roles|direct_permissions Choose seeder type
--with-users Export users based on their roles/permissions
--all Export all tenants/users regardless of role assignments
--include-passwords Include existing hashed passwords from database
--generate-passwords= Generate passwords (random, prompt, or custom value)
--force Overwrite existing seeder file
When no user exists yet, shield:super-admin prompts for a name, email, and password. If your User model requires more than that, register a closure via SuperAdminCommand::createSuperAdminUsing() in a service provider's boot() method and the command will call it instead:
use BezhanSalleh\FilamentShield\Commands\SuperAdminCommand;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Hash;
use function Laravel\Prompts\password;
use function Laravel\Prompts\text;
public function boot(): void
{
SuperAdminCommand::createSuperAdminUsing(function (): ?Authenticatable {
return \App\Models\User::create([
'name' => text(label: 'First Name', required: true),
'last_name' => text(label: 'Last Name', required: true),
'email' => text(
label: 'Email address',
required: true,
validate: fn (string $email): ?string => match (true) {
! filter_var($email, FILTER_VALIDATE_EMAIL) => 'The email address must be valid.',
\App\Models\User::where('email', $email)->exists() => 'A user with this email address already exists.',
default => null,
},
),
'password' => Hash::make(password(
label: 'Password',
required: true,
validate: fn (string $value): ?string => match (true) {
strlen($value) < 8 => 'The password must be at least 8 characters.',
default => null,
},
)),
]);
});
SuperAdminCommand::prohibit($this->app->isProduction());
}
The closure is resolved through the container and should return an Authenticatable instance. Returning null deliberately falls back to the built-in interactive prompts, so a closure can hand control back whenever it decides not to create the user itself.
Keep in mind that the closure replaces Shield's built-in email and password validation entirely β validate whatever you collect, and never hardcode credentials in it. This hook is meant for bootstrapping development and staging environments only; in production, create the super admin through a seeder or a deliberate one-time run, and pair the hook with SuperAdminCommand::prohibit($this->app->isProduction()) in the same boot() as shown above.
Shield supports multiple languages out of the box. When enabled, you can provide translated labels for permissions to create a more localized experience for your app's users.
'localization' => [
'enabled' => false,
'key' => 'shield-permissions', // could be any name you want
],
Shield uses a fallback chain for resolving permission labels:
localization.enabled = true)
lang/{locale}/{key}.php where {key} is your configured localization keyresource_permission_prefixes_labels for standard affixes (view, create, update, etc.)force_delete_any β "Force Delete Any")The easiest way to create a translation file is using the shield:translation command:
php artisan shield:translation en --panel=admin
This generates a file at lang/en/shield-permissions.php containing all permission labels:
<?php
/**
* Shield Permission Labels
*
* Translate the values below to localize permission labels in your application.
*/
return [
// Resource affixes
'create' => 'Create',
'delete' => 'Delete',
'delete_any' => 'Delete Any',
'force_delete' => 'Force Delete',
'force_delete_any' => 'Force Delete Any',
'replicate' => 'Replicate',
'reorder' => 'Reorder',
'restore' => 'Restore',
'restore_any' => 'Restore Any',
'update' => 'Update',
'view' => 'View',
'view_any' => 'View Any',
// Pages (permission key in snake_case)
'view_dashboard' => 'Dashboard',
// Widgets (permission key in snake_case)
'view_stats_overview' => 'Stats Overview',
// Custom permissions
'approve_posts' => 'Approve Posts',
];
All translation keys are in snake_case format:
| Permission Type | Original Key | Translation Key |
|-----------------|--------------|-----------------|
| Resource affix | viewAny | view_any |
| Resource affix | forceDeleteAny | force_delete_any |
| Page permission | view:Dashboard | view_dashboard |
| Widget permission | view:StatsOverview | view_stats_overview |
| Custom permission | Approve:Posts | approve_posts |
Shield includes translations for standard resource affixes in 32 languages. When localization.enabled = false,
the package automatically uses these translations for affixes like view, create, update, delete, etc.
For entity labels (Resources, Pages, Widgets), Filament's entity related methods are used
(getModelLabel(), getTitle(), getHeading(), etc.).
Upgrading from 3.x|4.0.0-Beta* versions to 4.x requires careful consideration due to significant changes in the package's architecture and functionality. Here are the key steps and considerations for a successful upgrade:
Backup Your Data: Before making any changes, ensure you have a complete backup of your database and application files. This is crucial in case you need to revert to the previous version.
Remove Config and Resource: Delete the existing filament-shield.php config file and the published RoleResource if you have done so. This is important to avoid conflicts with the new configuration and resource structure.
Update Composer: Run composer require bezhansalleh/filament-shield to update the package to the latest version.
Publish New Config and Resource: Publish the new configuration file and the RoleResource using the following commands:
php artisan vendor:publish --tag="filament-shield-config"
php artisan shield:publish --panel=admin # you can ignore this if you didn't published the resource previously
Adjust Configuration: Review and adjust the new filament-shield.php configuration file to match your application's requirements. Pay special attention to the new options and defaults that may differ from the previous version.
HasShieldPermissions Contract is Deprecated: If you have implemented the HasShieldPermissions contract in your resources, consult Policies and Resources sections on how to migrate. If you leave it as is, it will be ignored.
Clean Slate or Perserve: Decide whether to start fresh with a clean slate or preserve existing roles and permissions.
php artisan shield:setup --fresh
AppServiceProvider's boot() method to perserve the the previous versions(3.x|4.x-Beta*) permission pattern:
use BezhanSalleh\FilamentShield\Facades\FilamentShield;
use Filament\Pages\BasePage as Page;
use Filament\Resources\Resource;
use Filament\Widgets\Widget;
use Illuminate\Support\Str;
//...
public function boot(): void
{
FilamentShield::buildPermissionKeyUsing(
function (string $entity, string $affix, string $subject, string $case, string $separator) {
return match(true) {
# if `configurePermissionIdentifierUsing()` was used previously, then this needs to be adjusted accordingly
is_subclass_of($entity, Resource::class) => Str::of($affix)
->snake()
->append('_')
->append(
Str::of($entity)
->afterLast('\\')
->beforeLast('Resource')
->replace('\\', '')
->snake()
->replace('_', '::')
)
->toString(),
is_subclass_of($entity, Page::class) => Str::of('page_')
->append(class_basename($entity))
->toString(),
is_subclass_of($entity, Widget::class) => Str::of('widget_')
->append(class_basename($entity))
->toString()
};
});
}
configurePermissionIdentifierUsing() method to customize the permission key composition, then adjust the logic for resources above to match your custom logic.shield:generate command
php artisan shield:generate --resource=FooResource,BarResource --option=policies
php artisan shield:generate --all --option=policies
--relationships flag to the above commands.Test Thoroughly: After completing the upgrade, thoroughly test your application to ensure that all functionalities related to roles, permissions, and access control are working as expected. Pay special attention to any custom implementations you may have had in place.
Publish the translations using:
php artisan vendor:publish --tag="filament-shield-translations"
composer test
See CHANGELOG.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.