DEV3 Technologies home

Filament canAccessPanel role guard on the user model

Decide which Filament 4 panel each user may open with a role enum and canAccessPanel, including active and approval checks.

FilamentPermissionsfilamentrolespanelsauthorization

Code

<?php

namespace App\Enums;

enum Role: string
{
    case Admin = 'admin';
    case Vendor = 'vendor';
    case Customer = 'customer';

    /** Panel ids this role may enter. Admins may also look at the vendor panel. */
    public function panels(): array
    {
        return match ($this) {
            self::Admin => ['admin', 'vendor'],
            self::Vendor => ['vendor', 'customer'],
            self::Customer => ['customer'],
        };
    }

    public function canEnter(string $panelId): bool
    {
        return in_array($panelId, $this->panels(), true);
    }
}

Needs: filament/filament@^4.0

How it works

What it solves

When one application hosts several Filament panels, a login only proves identity. Without a guard, a customer could open /admin as soon as they authenticate. canAccessPanel() is the single place where "may this user enter this panel" is answered.

How it works

The user model implements FilamentUser. Filament calls canAccessPanel(Panel $panel) on every request to a panel, in production, and refuses access with a 403 when it returns false.

The role lives in a backed enum cast, and each case lists the panel ids it may enter through panels(). The method then layers rules from cheapest to most specific: an inactive account is blocked everywhere, then the role must match the panel id, then panel-specific business rules apply. Vendors need an approval timestamp before the vendor panel opens, and the admin panel needs a verified email address.

Keeping the role-to-panel table inside the enum means a new panel or role touches one file, and the check stays readable and testable without booting Filament.

Adapting it

Replace the role enum with spatie/laravel-permission roles if you already use it ($this->hasAnyRole([...])). Add a team or subscription check for paid panels. If you only have a few emails to trust, move that rule to config rather than hard-coding anything in the model.

Pitfalls

  • Locally Filament lets any user in when the app environment is local and the contract is not implemented; implement it early so you test the real behaviour.
  • This guards panel entry only. Resource-level access still needs policies.
  • Remember that changing a role does not end existing sessions; the check runs per request, so it takes effect immediately.

How to use it

  1. Add a role string column (default customer), is_active boolean and vendor_approved_at timestamp to users.
  2. Copy Role.php into app/Enums/ and merge the casts and canAccessPanel() into your User model.
  3. Use panel ids that match your providers (admin, vendor, customer).
  4. Test: log in as each role and open every panel URL; expect a 403 where not allowed.
  5. Add policies on resources for finer rules inside a panel.

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets