Appearance
📱 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.