Search by

andika / tameng

andika

Custom Filament plugin for role and permission management

1.4.0 2026-10-05 06:34 UTC

This package is auto-updated.

Last update: 2026-10-30 01:34:54 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Tameng adds role & permission management to your Filament panels on top of spatie/laravel-permission.

Features

  • πŸ›‘οΈ Role Management β€” manage roles and permissions from a single page with embedded permission cards
    • πŸ“¦ Resource Permissions
    • πŸ“„ Page Permissions
    • 🧩 Widget Permissions
    • ✨ Custom Permissions
  • πŸ€– Automatic Permission Generation β€” scans panels and creates permissions by convention
    • πŸ“œ Policy generation from publishable stubs
    • 🏷️ Configurable case formatting (snake, kebab, pascal, camel, upper_snake)
    • πŸ”— Entity discovery across all panels
  • πŸ‘‘ Super Admin Bypass β€” users with the configured role skip all authorization checks
  • πŸ”„ Multi-tenancy Support β€” automatic tenant scoping with spatie teams + Filament panel tenancy
  • 🎨 Intuitive UI β€” Shield-inspired card grid layout with icons per entity type
  • ⚑ Fine-grained CLI Tooling β€” install, generate, and super-admin commands

Requirements

  • PHP ^8.3
  • Laravel 12+
  • Filament 5.x (panels)
  • spatie/laravel-permission ^8.0

Installation

Install the package and run the installer:

composer require andika/tameng
php artisan tameng:install

The installer publishes Spatie's config/permission.php and the create_permission_tables migration, publishes our config/tameng.php, and asks whether you want to run php artisan migrate. After installation, it shows next steps:

tameng has been installed!

Next Steps:
  1. php artisan tameng:generate
     Generate permissions for your Filament panels
  2. php artisan tameng:super-admin
     Assign super admin to a user (interactive)
  3. php artisan permission:cache-reset
     Clear permission cache

Add the HasRoles trait to your User model:

use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;
}

Register the plugin on your panel provider:

use Andika\Tameng\TamengPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugin(TamengPlugin::make());
}

Usage

Quickstart

php artisan tameng:super-admin     # create your first admin (interactive)
php artisan tameng:generate        # permissions + policies for panel entities
php artisan permission:cache-reset # refresh the spatie cache

Log in at your panel's URL (e.g. /dashboard) β€” the Tameng navigation item appears in the sidebar. Open it to manage roles and their permissions via embedded permission cards.

Super admin role

Users with the role configured in config/tameng.php bypass every authorization check:

'super_admin' => [
    'enabled' => false,  // set to true to enable the bypass
    'name' => 'super_admin',
],

Set enabled to true to activate the bypass β€” any user with this role skips all authorization checks. The role then behaves like any other when disabled.

Assign the role from the command line:

php artisan tameng:super-admin

The command picks a panel from --panel (falling back to the current or the only registered panel), then:

  • --user= β€” the ID of the user to promote (no prompt)
  • no users β€” asks for a name, email and password and creates the user before assigning the role
  • one user β€” promotes them automatically
  • multiple users β€” lists them and asks for the ID to promote
php artisan tameng:super-admin --panel=admin --user=1

With spatie teams enabled, pass the team/tenant the role should be scoped to:

php artisan tameng:super-admin --tenant=1

Or do it in code:

Role::findOrCreate('super_admin');
$user->assignRole('super_admin');

Permissions & policies

Generate permissions and policies for every entity registered in your panels:

php artisan tameng:generate

This creates permissions such as user_view_any, user_view, user_create, user_update, user_delete, user_delete_any and writes app/Policies/UserPolicy.php:

namespace App\Policies;

class UserPolicy
{
    public function viewAny($user): bool
    {
        return $user->can('user_view_any');
    }

    public function view($user, $model): bool
    {
        return $user->can('user_view');
    }

    // ...
}

Existing policies are never overwritten unless you pass --force:

php artisan tameng:generate --force

Generate permissions for a single panel only:

php artisan tameng:generate --panel=admin

Policies are rendered from a stub. To customize the generated policy shape, publish the stub and edit it (placeholders: {namespace}, {class}, {methods}):

php artisan vendor:publish --tag="tameng-stubs"

After changing permissions, refresh the permission cache:

php artisan permission:cache-reset

Configuration

All options live in config/tameng.php. Publish it with:

php artisan vendor:publish --tag="tameng-config"

After upgrading the package: vendor:publish does not overwrite an existing config/tameng.php. Your published copy keeps missing any option added in the new release, while the package still reads those keys with its own defaults β€” so behavior can change without a matching line in your config file.

Example: v1.1 added permission.enforce_page_permissions (default true). Apps that published config earlier never got that key; middleware still ran because of the default, which is easy to misread as β€œmiddleware is off / not registered.”

After each upgrade, either:

  1. Merge manually (recommended if you customized the file) β€” open the package’s vendor/andika/tameng/config/tameng.php, diff it against yours, and add only the keys you’re missing; or
  2. Re-publish with --force (resets local edits β€” back up first if you changed anything):
php artisan vendor:publish --tag="tameng-config" --force

Permission builder

'permission' => [
    'separator' => '_',
    'case' => 'snake',       // snake|kebab|pascal|camel|upper_snake|lower_snake
    'generate' => true,      // false skips permission creation (policies still written)
    'name_max_length' => 255,
    'scoped_to_panel' => false,
    'enforce_page_permissions' => true, // registers EnsurePagePermission on panel routes
],

Separator + case constraint: _ cannot pair with snake/upper_snake, - cannot pair with kebab. Examples: pascal + : β†’ User:ViewAny; kebab + - β†’ user-view-any.

Slug

The URL path for the role management page. Defaults to tameng (e.g. /dashboard/tameng):

'slug' => 'tameng',   // β†’ /dashboard/tameng

Navigation

Customize the sidebar navigation item for the role management page:

'navigation' => [
    'group' => 'Access',              // sidebar group label
    'label' => 'Tameng',              // display text in sidebar
    'icon' => Heroicon::ShieldCheck,  // sidebar icon (Heroicon enum or string)
    'sort' => null,                   // sort order within group (null = alphabetical)
],

Per-panel overrides via the plugin API:

TamengPlugin::make()
    ->navigationGroup('Settings')
    ->navigationLabel('Roles')
    ->navigationIcon(Heroicon::Key)
    ->navigationSort(10);

Excludes

Skip entities from permission generation:

'resources' => [
    'subject' => 'model',    // 'model' uses the resource's model class name
    'exclude' => [
        // \App\Filament\Resources\FooResource::class,
    ],
],

'pages' => [
    'exclude' => [
        \Filament\Pages\Dashboard::class,
    ],
],

'widgets' => [
    'exclude' => [
        \Filament\Widgets\AccountWidget::class,
        \Filament\Widgets\FilamentInfoWidget::class,
    ],
],

Custom permissions

Extra permission keys created by tameng:generate alongside entity-derived permissions:

'custom_permissions' => [
    'system_backup',
    'audit_log',
],

Policies

'policies' => [
    'path' => app_path('Policies'),
    'namespace' => 'App\\Policies',
    'methods' => [
        'view_any', 'view', 'create', 'update', 'delete',
        'delete_any', 'restore', 'restore_any',
        'force_delete', 'force_delete_any',
    ],
    'single_parameter_methods' => [
        'view_any', 'create', 'delete_any',
        'restore_any', 'force_delete_any',
    ],
],

Methods in single_parameter_methods generate ($user) signatures; others generate ($user, $model).

Set register_role_policy to true to generate a RolePolicy for the spatie Role model and register it with Gate::policy:

'register_role_policy' => true,

Tenant model

Tameng integrates with Filament's multi-tenancy system and spatie teams. When both are enabled:

  1. A SyncTenant middleware automatically syncs the spatie team ID from the current Filament tenant
  2. The role list is scoped to the current tenant
  3. New roles are automatically associated with the current tenant
  4. Permission cache is only invalidated when the tenant changes

Setup:

  1. Enable spatie teams in config/permission.php:
'teams' => true,
  1. Set your tenant model in config/tameng.php:
'tenant_model' => App\Models\Team::class,
  1. Register the tenant on your Filament panel:
use App\Models\Team;

return $panel
    ->tenant(Team::class)
    ->plugin(TamengPlugin::make());
  1. Make your User model implement Filament\Models\Contracts\HasTenants:
use Filament\Models\Contracts\HasTenants;
use Filament\Panel;

class User extends Authenticatable implements HasTenants
{
    use HasRoles;

    public function tenants(Panel $panel): Collection
    {
        return $this->teams;
    }

    public function canAccessTenant(Model $tenant): bool
    {
        return true;
    }
}
  1. Assign roles scoped to a tenant via CLI:
php artisan tameng:super-admin --user=1 --tenant=1

Entity discovery

When enabled (default), tameng:generate scans every registered panel and generates permissions and policies for all of its resources, pages and widgets. Disable globally via config or per-panel via the plugin API:

'entity_discovery' => true,
TamengPlugin::make()
    ->entityDiscovery(false);   // disable for this panel only

Localization

Translated permission labels in the role editor:

'localization' => [
    'enabled' => false,
    'key' => 'tameng::tameng.permissions',
],

Publish translations:

php artisan vendor:publish --tag="tameng-translations"

Plugin configuration

Per-panel overrides via the plugin API:

TamengPlugin::make()
    ->superAdminRole('super_admin')   // per-panel role name override
    ->entityDiscovery(false);         // disable entity scanning in tameng:generate

Ownership enforcement

By default, any user with student_update can update any student record. Enable ownership enforcement to require record ownership:

'policies' => [
    'ownership' => [
        'enabled' => true,
        'foreign_key' => 'user_id',  // column to check ownership
        'resolver' => null,          // optional custom closure
    ],
],

With ownership enabled, generated policies check both permission and ownership:

public function update(User $user, Student $model): bool
{
    if (! $user->can('student_update')) {
        return false;
    }

    return $model->user_id === $user->id;
}

Or use a custom resolver for complex relationships:

'ownership' => [
    'enabled' => true,
    'resolver' => function ($model, $user) {
        return $model->user_id === $user->id && $user->isActive();
    },
],

Enable ownership via CLI flag:

php artisan tameng:generate --with-ownership

Multi-panel permission scoping

When using multiple Filament panels, enable panel-scoped permissions to prefix permission names with the panel ID:

'permission' => [
    'scoped_to_panel' => true,  // admin_post_view β‰  web_post_view
],

This creates panel-specific permissions like admin_post_view and web_post_view.

Lifecycle hooks

Add before and after callbacks to generated policies for custom logic like audit trails:

'policies' => [
    'before' => function ($user, $ability, $model) {
        // Runs before permission check
        return null; // Continue to permission check
    },
    'after' => function ($user, $ability, $model, $result) {
        // Runs after permission check
        return $result;
    },
],

Validating permissions

Check that all permissions are consistent across your panels and policies:

php artisan tameng:check

Output:

βœ“ All permissions are consistent.

Or identify missing permissions:

βœ— App\Filament\Resources\PostResource expects permission "post_view_any" β€” not defined

Syncing permissions

After adding new resources, sync missing permissions without overwriting existing ones:

php artisan tameng:sync --dry-run   # preview what would be created
php artisan tameng:sync             # create missing permissions

HasPermissionCheck trait

Use the HasPermissionCheck trait on Filament resources to auto-generate canViewAny():

use Andika\Tameng\Support\Concerns\HasPermissionCheck;

class CategoryResource extends Resource
{
    use HasPermissionCheck;
    // Automatically checks category_view_any permission
}

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.