Frontend

laravel-filament

Builds Filament admin panels with resources, pages, widgets

Overview

The laravel-filament agent creates admin panel components using Filament. It generates resources for CRUD interfaces, custom pages, dashboard widgets, and handles form/table builders with proper validation and relationships.

Responsibilities

  • Resources - CRUD interfaces with forms, tables, and relation managers
  • Form Builder - Complex forms with sections, tabs, and conditional fields
  • Table Builder - Searchable, sortable tables with filters and bulk actions
  • Custom Pages - Dashboard pages, settings pages, reports
  • Widgets - Stats, charts, and custom dashboard widgets
  • Relation Managers - Nested resource management

What It Creates

Component Location Purpose
Resource app/Filament/Resources/ CRUD interface for a model
Pages app/Filament/Pages/ Custom admin pages
Widgets app/Filament/Widgets/ Dashboard components
Relation Managers app/Filament/Resources/*/RelationManagers/ Nested CRUD for relationships

Generated Resource Example

<?php

namespace App\Filament\Resources;

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

class ProductResource extends Resource
{
    protected static ?string $model = Product::class;
    protected static ?string $navigationIcon = 'heroicon-o-shopping-bag';
    protected static ?string $navigationGroup = 'Shop';

    public static function form(Form $form): Form
    {
        return $form->schema([
            Forms\Components\Section::make('Product Details')
                ->schema([
                    Forms\Components\TextInput::make('name')
                        ->required()
                        ->maxLength(255)
                        ->live(onBlur: true)
                        ->afterStateUpdated(fn ($state, $set) =>
                            $set('slug', str($state)->slug())),

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

                    Forms\Components\RichEditor::make('description')
                        ->columnSpanFull(),

                    Forms\Components\TextInput::make('price')
                        ->required()
                        ->numeric()
                        ->prefix('$'),

                    Forms\Components\Select::make('category_id')
                        ->relationship('category', 'name')
                        ->searchable()
                        ->preload()
                        ->createOptionForm([
                            Forms\Components\TextInput::make('name')->required(),
                        ]),

                    Forms\Components\FileUpload::make('image')
                        ->image()
                        ->directory('products'),
                ])->columns(2),
        ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\ImageColumn::make('image'),
                Tables\Columns\TextColumn::make('name')
                    ->searchable()
                    ->sortable(),
                Tables\Columns\TextColumn::make('category.name')
                    ->sortable(),
                Tables\Columns\TextColumn::make('price')
                    ->money()
                    ->sortable(),
                Tables\Columns\IconColumn::make('is_active')
                    ->boolean(),
            ])
            ->filters([
                Tables\Filters\SelectFilter::make('category')
                    ->relationship('category', 'name'),
                Tables\Filters\TernaryFilter::make('is_active'),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    Tables\Actions\DeleteBulkAction::make(),
                ]),
            ]);
    }

    public static function getRelations(): array
    {
        return [
            RelationManagers\ReviewsRelationManager::class,
        ];
    }
}

Stats Widget Example

<?php

namespace App\Filament\Widgets;

use App\Models\Order;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;

class OrderStats extends StatsOverviewWidget
{
    protected function getStats(): array
    {
        return [
            Stat::make('Total Orders', Order::count())
                ->description('All time orders')
                ->descriptionIcon('heroicon-m-arrow-trending-up')
                ->color('success'),

            Stat::make('Revenue', '$' . number_format(Order::sum('total'), 2))
                ->description('Total revenue')
                ->chart([7, 3, 4, 5, 6, 3, 5, 8])
                ->color('primary'),

            Stat::make('Pending', Order::where('status', 'pending')->count())
                ->description('Awaiting processing')
                ->color('warning'),
        ];
    }
}

Custom Action Example

// In table actions
Tables\Actions\Action::make('approve')
    ->action(fn (Product $record) => $record->approve())
    ->requiresConfirmation()
    ->modalHeading('Approve Product')
    ->modalDescription('Are you sure you want to approve this product?')
    ->color('success')
    ->icon('heroicon-o-check'),

// Bulk action
Tables\Actions\BulkAction::make('export')
    ->action(function (Collection $records) {
        return Excel::download(
            new ProductsExport($records),
            'products.xlsx'
        );
    })
    ->icon('heroicon-o-arrow-down-tray')

Invoked By Commands

Called By

Best Practices

  • Use Sections - Organize forms with sections and tabs
  • Add searchable - Enable search on Select fields
  • Relation managers - Use for nested CRUD operations
  • Global search - Configure $recordTitleAttribute

Guardrails

The Filament agent follows strict rules:

  • ALWAYS set navigationIcon for resources
  • ALWAYS implement proper authorization
  • ALWAYS add soft deletes with restore action
  • NEVER exceed 15 fields per form section
  • NEVER skip relationship configuration on Select fields

See Also