Cover image for Building Powerful Admin Panels with Laravel 12 and Filament v5: A Production Guide

At a glance

Reading time

~200 words/min

Published

3 hours ago

May 17, 2026

Views

9

All-time total

Building Powerful Admin Panels with Laravel 12 and Filament v5: A Production Guide

Filament v5 is the version that finally makes "build the admin panel" a one-evening task instead of a two-week project. Released alongside Laravel 12, it ships with rebuilt forms and tables, native multi-tenancy, and a plugin ecosystem that genuinely covers the long tail. This guide is what I wish someone had handed me on day one the patterns that scale to ten resources, fifty, and beyond.

What this guide covers

  • Installing Filament v5 cleanly on a fresh Laravel 12 app
  • Designing your first Resource with relations, filters, and bulk actions
  • Role-based access with Spatie Permissions
  • Multi-tenancy: tenant-scoped resources without query leaks
  • Theming, custom widgets, and a deployment checklist
i

Info

Filament v5 in one sentence

A Livewire-powered admin panel framework that gives you tables, forms, and dashboards in a few PHP classes — no separate frontend, no API, no JSON contract to maintain.

1. Installation that actually works on day 30

composer require filament/filament:"^5.0" -W
php artisan filament:install --panels
php artisan make:filament-user

The default panel lives at /admin. Before you ship anything, lock it down.

// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        ->default()
        ->id('admin')
        ->path('admin')
        ->login()
        ->authGuard('web')
        ->colors(['primary' => Color::Teal])
        ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
        ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
        ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
        ->middleware([
            EncryptCookies::class,
            AddQueuedCookiesToResponse::class,
            StartSession::class,
            AuthenticateSession::class,
            ShareErrorsFromSession::class,
            VerifyCsrfToken::class,
            SubstituteBindings::class,
            DisableBladeIconComponents::class,
            DispatchServingFilamentEvent::class,
        ])
        ->authMiddleware([Authenticate::class, EnsureUserCanAccessFilament::class]);
}

Warning

Default access policy is too permissive

Filament out of the box lets any authenticated user reach <code>/admin</code>. Add an <code>EnsureUserCanAccessFilament</code> middleware that checks <code>$user-&gt;is_admin</code> or a Spatie role. Without it, your customer support team logs in to the admin panel by accident.

2. Your first Resource : done right

php artisan make:filament-resource Project --generate
// app/Filament/Resources/ProjectResource.php
public static function form(Form $form): Form
{
    return $form->schema([
        Forms\Components\Section::make('Project details')
            ->columns(2)
            ->schema([
                Forms\Components\TextInput::make('name')
                    ->required()
                    ->maxLength(120)
                    ->live(onBlur: true)
                    ->afterStateUpdated(fn ($set, $state) => $set('slug', Str::slug($state))),

                Forms\Components\TextInput::make('slug')
                    ->required()
                    ->unique(ignoreRecord: true),

                Forms\Components\Select::make('owner_id')
                    ->relationship('owner', 'name')
                    ->searchable()
                    ->preload()
                    ->required(),

                Forms\Components\Select::make('status')
                    ->options(['draft' => 'Draft', 'active' => 'Active', 'archived' => 'Archived'])
                    ->default('draft')
                    ->required(),

                Forms\Components\RichEditor::make('description')
                    ->columnSpanFull()
                    ->disableToolbarButtons(['attachFiles']),
            ]),
    ]);
}

public static function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
            Tables\Columns\TextColumn::make('owner.name')->label('Owner')->sortable(),
            Tables\Columns\BadgeColumn::make('status')
                ->colors(['gray' => 'draft', 'success' => 'active', 'warning' => 'archived']),
            Tables\Columns\TextColumn::make('updated_at')->since()->sortable(),
        ])
        ->filters([
            Tables\Filters\SelectFilter::make('status')->options(['draft', 'active', 'archived']),
            Tables\Filters\Filter::make('mine')
                ->toggle()
                ->query(fn ($query) => $query->where('owner_id', auth()->id())),
        ])
        ->actions([Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make()])
        ->bulkActions([Tables\Actions\DeleteBulkAction::make()])
        ->defaultSort('updated_at', 'desc');
}
💡

Pro tip

Always pass <code>ignoreRecord: true</code> on unique validators inside Resources without it, editing a record fails because Filament re-validates the existing slug against itself.

3. Role-based access with Spatie Permissions

composer require spatie/laravel-permission
// app/Filament/Resources/ProjectResource.php
public static function canViewAny(): bool   { return auth()->user()->can('view-any project'); }
public static function canCreate(): bool    { return auth()->user()->can('create project'); }
public static function canEdit($record): bool   { return auth()->user()->can('update', $record); }
public static function canDelete($record): bool { return auth()->user()->can('delete', $record); }

Wire the same permissions into your model policies and you get one source of truth, Filament inherits the policy automatically when you implement canEdit via the Gate.

4. Multi-tenancy without leaks

Filament v5 has first-class tenancy. Tell the panel which model represents a tenant and every resource is scoped automatically.

// In AdminPanelProvider::panel()
->tenant(Workspace::class, slugAttribute: 'slug')
->tenantRegistration(RegisterWorkspace::class)
->tenantProfile(EditWorkspaceProfile::class)
// app/Models/Workspace.php
public function getFilamentName(): string { return $this->name; }
public function canAccessFilament(): bool { return $this->users->contains(auth()->user()); }

// app/Models/User.php
public function getTenants(Panel $panel): Collection
{
    return $this->workspaces;
}

public function canAccessTenant(Model $tenant): bool
{
    return $this->workspaces->contains($tenant);
}

Danger

Tenant scoping is opt-in per resource

Resources that have a direct relationship to the tenant model auto-scope. Resources that go through pivot tables or polymorphic relations need explicit query overrides. Audit every Resource: open the table, switch tenants, confirm zero rows leak. Do this <em>before</em> any customer sees the panel.

5. Custom widgets that earn their keep

// app/Filament/Widgets/ProjectsOverview.php
class ProjectsOverview extends BaseWidget
{
    protected function getStats(): array
    {
        $tenant = Filament::getTenant();

        return [
            Stat::make('Active projects', $tenant->projects()->where('status', 'active')->count())
                ->description('Last 30 days')
                ->descriptionIcon('heroicon-o-arrow-trending-up')
                ->color('success'),

            Stat::make('Open tasks', $tenant->tasks()->whereNull('completed_at')->count())
                ->color('warning'),

            Stat::make('Avg. response time', '4h 12m')
                ->color('primary'),
        ];
    }
}
60%

less time spent on CRUD vs hand-rolling Blade + Tailwind from scratch

Pros

  • Resources cover 80% of admin needs out of the box
  • First-class multi-tenancy and RBAC
  • Plugin ecosystem (Shield for permissions, Spatie media library, etc.)
  • Looks polished without designer involvement

Cons

  • Customising deeply means writing Livewire fight or learn the framework
  • Bundle size is non-trivial; ship Filament behind auth, never on public pages
  • Octane compatibility has rough edges; test before enabling

Production checklist

1

Lock the panel behind a middleware

Even on staging. The number of leaked admin panels indexed by Google is depressing.

2

Cache permissions

<code>spatie.permission.cache</code> on Redis. Without it, every page load hits the permissions table.

3

Run `php artisan filament:optimize` on deploy

It precompiles Filament assets saves roughly 200ms on cold cache.

4

Set up tenant tests

A single Pest test that creates two tenants and asserts zero cross-leak prevents the worst class of bugs.

Success

You will reach for Filament more than you expect

Once a team ships their first panel they tend to consolidate every internal tool into Filament settings, billing review, customer support, audit logs. That is a feature, not a bug.

Newsletter

Want more posts like this?

Get practical software notes and tutorials delivered when something new is published.

No spam. Unsubscribe anytime.

Share

Related posts

CRUD Operations In Laravel 8

This tutorial is created to illustrate the basic CRUD (Create , Read, Update, Delete) operation using SQL with Laravel 8. Laravel is one of the fastest-growing frameworks for PHP.

4 years ago

Installing PHP on Windows

PHP is known to be one of the most commonly used server-side programming language on the web. It provides an easy to master with a simple learning curve. It has close ties with the MySQL database.

5 years ago

Scheduling Tasks with Cron Job in Laravel 5.8

Cron Job is used to schedule tasks that will be executed every so often. Crontab is a file that contains a list of scripts, By editing the Crontab, You can run the scripts periodically.

7 years ago

Connecting Multiple Databases in Laravel 5.8

This tutorial is created to implement multiple database connections using mysql. Let’s see how to configure multiple database connections in Laravel 5.8.

6 years ago

Integrating Google ReCaptcha in Laravel 5.8

reCAPTCHA is a free service from Google. It’s a CAPTCHA-like system designed to recognize that the user is human and, at the same time, assist in the digitization of books. It helps to protects your w

6 years ago