Skip to content

📱 Filament Dashboard

Filament is a powerful admin panel for Laravel applications. This section covers best practices for building maintainable and user-friendly admin interfaces.

🏗️ Project Structure

✅ Good Example

app/
├── Filament/
│   ├── Resources/
│   │   ├── UserResource.php
│   │   ├── PostResource.php
│   │   └── CategoryResource.php
│   ├── Pages/
│   │   ├── Dashboard.php
│   │   └── Settings.php
│   ├── Widgets/
│   │   ├── UserStatsWidget.php
│   │   └── RecentActivityWidget.php
│   └── Components/
│       ├── CustomTable.php
│       └── StatusBadge.php

❌ Bad Example

app/
├── Filament/
│   ├── UserResource.php
│   ├── PostResource.php
│   ├── CategoryResource.php
│   ├── Dashboard.php
│   ├── Settings.php
│   ├── UserStatsWidget.php
│   ├── RecentActivityWidget.php
│   ├── CustomTable.php
│   └── StatusBadge.php

📊 Resource Configuration

✅ Good Example

php
<?php

namespace App\Filament\Resources;

use App\Filament\Resources\UserResource\Pages;
use App\Models\User;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;

class UserResource extends Resource
{
    protected static ?string $model = User::class;
    protected static ?string $navigationIcon = 'heroicon-o-users';
    protected static ?string $navigationGroup = 'User Management';
    protected static ?int $navigationSort = 1;

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Forms\Components\Section::make('User Information')
                    ->schema([
                        Forms\Components\TextInput::make('name')
                            ->required()
                            ->maxLength(255),
                        Forms\Components\TextInput::make('email')
                            ->email()
                            ->required()
                            ->unique(ignoreRecord: true)
                            ->maxLength(255),
                        Forms\Components\TextInput::make('password')
                            ->password()
                            ->required(fn (string $context): bool => $context === 'create')
                            ->minLength(8)
                            ->dehydrated(fn ($state) => filled($state))
                            ->dehydrateStateUsing(fn ($state) => Hash::make($state)),
                    ])
                    ->columns(2),
                
                Forms\Components\Section::make('Role & Permissions')
                    ->schema([
                        Forms\Components\Select::make('role')
                            ->options([
                                'admin' => 'Administrator',
                                'user' => 'User',
                                'moderator' => 'Moderator',
                            ])
                            ->required(),
                        Forms\Components\Toggle::make('is_active')
                            ->default(true),
                    ])
                    ->columns(2),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('name')
                    ->searchable()
                    ->sortable(),
                Tables\Columns\TextColumn::make('email')
                    ->searchable()
                    ->sortable(),
                Tables\Columns\TextColumn::make('role')
                    ->badge()
                    ->color(fn (string $state): string => match ($state) {
                        'admin' => 'danger',
                        'moderator' => 'warning',
                        'user' => 'success',
                    }),
                Tables\Columns\IconColumn::make('is_active')
                    ->boolean(),
                Tables\Columns\TextColumn::make('created_at')
                    ->dateTime()
                    ->sortable()
                    ->toggleable(isToggledHiddenByDefault: true),
            ])
            ->filters([
                Tables\Filters\SelectFilter::make('role')
                    ->options([
                        'admin' => 'Administrator',
                        'user' => 'User',
                        'moderator' => 'Moderator',
                    ]),
                Tables\Filters\TernaryFilter::make('is_active')
                    ->label('Active Status'),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    Tables\Actions\DeleteBulkAction::make(),
                ]),
            ]);
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListUsers::route('/'),
            'create' => Pages\CreateUser::route('/create'),
            'edit' => Pages\EditUser::route('/{record}/edit'),
        ];
    }
}

❌ Bad Example

php
<?php

namespace App\Filament\Resources;

use App\Models\User;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;

class UserResource extends Resource
{
    protected static ?string $model = User::class;

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Forms\Components\TextInput::make('name'),
                Forms\Components\TextInput::make('email'),
                Forms\Components\TextInput::make('password'),
                Forms\Components\TextInput::make('role'),
                Forms\Components\TextInput::make('is_active'),
            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('name'),
                Tables\Components\TextColumn::make('email'),
                Tables\Columns\TextColumn::make('role'),
                Tables\Columns\TextColumn::make('is_active'),
                Tables\Columns\TextColumn::make('created_at'),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
            ]);
    }
}

🎨 Custom Components

✅ Good Example

php
<?php

namespace App\Filament\Components;

use Filament\Forms\Components\Component;
use Filament\Forms\Get;
use Filament\Forms\Set;

class StatusBadge extends Component
{
    protected string $view = 'filament.components.status-badge';

    public static function make(string $name): static
    {
        $static = app(static::class, ['name' => $name]);
        $static->configure();

        return $static;
    }

    public function getView(): string
    {
        return $this->view;
    }
}
blade
<!-- resources/views/filament/components/status-badge.blade.php -->
<div class="flex items-center space-x-2">
    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
        @if($getState() === 'active') bg-green-100 text-green-800
        @elseif($getState() === 'inactive') bg-red-100 text-red-800
        @else bg-gray-100 text-gray-800
        @endif">
        {{ ucfirst($getState()) }}
    </span>
</div>

❌ Bad Example

php
<?php

// Inline HTML in component - not maintainable
class StatusBadge extends Component
{
    public function render(): string
    {
        $status = $this->getState();
        return "<span class='badge badge-{$status}'>{$status}</span>";
    }
}

📈 Widget Development

✅ Good Example

php
<?php

namespace App\Filament\Widgets;

use App\Models\User;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;

class UserStatsWidget extends BaseWidget
{
    protected function getStats(): array
    {
        return [
            Stat::make('Total Users', User::count())
                ->description('All registered users')
                ->descriptionIcon('heroicon-m-users')
                ->color('success'),
            
            Stat::make('Active Users', User::where('is_active', true)->count())
                ->description('Currently active users')
                ->descriptionIcon('heroicon-m-check-circle')
                ->color('success'),
            
            Stat::make('New Users This Month', User::whereMonth('created_at', now()->month)->count())
                ->description('Registered this month')
                ->descriptionIcon('heroicon-m-calendar')
                ->color('info'),
            
            Stat::make('Admin Users', User::where('role', 'admin')->count())
                ->description('Administrator accounts')
                ->descriptionIcon('heroicon-m-shield-check')
                ->color('warning'),
        ];
    }
}

❌ Bad Example

php
<?php

// Hard-coded values and no proper structure
class UserStatsWidget extends BaseWidget
{
    protected function getStats(): array
    {
        return [
            Stat::make('Users', '150'),
            Stat::make('Active', '120'),
            Stat::make('New', '25'),
            Stat::make('Admin', '5'),
        ];
    }
}

🔐 Permission Management

✅ Good Example

php
<?php

namespace App\Filament\Resources;

use Filament\Resources\Resource;

class UserResource extends Resource
{
    public static function canViewAny(): bool
    {
        return auth()->user()->can('view_any_user');
    }

    public static function canCreate(): bool
    {
        return auth()->user()->can('create_user');
    }

    public static function canEdit($record): bool
    {
        return auth()->user()->can('edit_user', $record);
    }

    public static function canDelete($record): bool
    {
        return auth()->user()->can('delete_user', $record);
    }
}

❌ Bad Example

php
<?php

// No permission checks - security risk
class UserResource extends Resource
{
    public static function canViewAny(): bool
    {
        return true; // Anyone can view
    }

    public static function canCreate(): bool
    {
        return true; // Anyone can create
    }

    public static function canEdit($record): bool
    {
        return true; // Anyone can edit
    }

    public static function canDelete($record): bool
    {
        return true; // Anyone can delete
    }
}

🎯 Best Practices

✅ Do's

  • Organize Resources in proper directory structure
  • Use Sections to group related form fields
  • Implement Proper Validation for all inputs
  • Add Search and Filters to tables
  • Use Badges and Icons for better UX
  • Implement Permissions for security
  • Create Reusable Components for consistency
  • Use Widgets for dashboard statistics

❌ Don'ts

  • Don't put all resources in one directory
  • Don't skip form validation
  • Don't ignore permission checks
  • Don't use inline HTML in components
  • Don't hard-code values in widgets
  • Don't skip error handling
  • Don't ignore responsive design
  • Don't forget to test admin functionality

🚀 Performance Optimization

✅ Good Example

php
<?php

// Eager loading relationships
public static function table(Table $table): Table
{
    return $table
        ->query(User::with(['profile', 'roles']))
        ->columns([
            Tables\Columns\TextColumn::make('profile.company')
                ->label('Company'),
            Tables\Columns\TextColumn::make('roles.name')
                ->label('Roles')
                ->badge(),
        ]);
}

❌ Bad Example

php
<?php

// N+1 query problem
public static function table(Table $table): Table
{
    return $table
        ->columns([
            Tables\Columns\TextColumn::make('profile.company')
                ->label('Company'), // This will cause N+1 queries
            Tables\Columns\TextColumn::make('roles.name')
                ->label('Roles'), // This will also cause N+1 queries
        ]);
}

📝 Filament Updates: Keep Filament updated to the latest version for security patches and new features. Always test admin functionality after updates.

Built with VitePress