Skill v1.0.1
currentAutomated scan100/100~1 modified, -2 removed
version: "1.0.1" name: saucebase-filament-development description: Develop Filament resources inside Saucebase modules, including tables, forms, infolists, pages, actions, filters, navigation groups, and tests. Use whenever creating or changing module Filament functionality.
Saucebase Filament Development
When to Use
Activate this skill when:
- Creating Filament resources (tables, forms, infolists, pages) inside any module
- Adding actions, filters, or bulk actions to Filament tables
- Registering navigation groups or panel configuration in a module
- Testing Filament resources
Resource Directory Structure
Resources in modules are split into separate files for Form, Table, Infolist, and Pages. Each resource lives in its own subdirectory:
modules/<Name>/app/Filament/<Name>Plugin.phpResources/<Model>Resource/<Model>Resource.phpSchemas/<Model>Form.php # Form schema (create/edit)<Model>Infolist.php # Infolist schema (view — optional)Tables/<Model>Table.php # Table schemaPages/List<Models>.phpCreate<Model>.phpEdit<Model>.phpView<Model>.php # optional
Plugin Pattern
The plugin is auto-discovered by convention at Modules\{Name}\Filament\{Name}Plugin — no manual registration needed.
namespace Modules\Feature\Filament;use App\Filament\ModulePlugin;use Filament\Contracts\Plugin;use Filament\Panel;class FeaturePlugin implements Plugin{use ModulePlugin;public function getModuleName(): string { return 'Feature'; }public function getId(): string { return 'feature'; }public function boot(Panel $panel): void{// Optional: register navigation groups, custom pages, etc.}}
The ModulePlugin trait auto-discovers and registers all Resources, Pages, Widgets, and Livewire components inside app/Filament/.
Resource Pattern
namespace Modules\Feature\Filament\Resources\FeatureResource;use Filament\Resources\Resource;use Filament\Schemas\Schema;use Filament\Tables\Table;use Modules\Feature\Filament\Resources\FeatureResource\Pages;use Modules\Feature\Filament\Resources\FeatureResource\Schemas\FeatureForm;use Modules\Feature\Filament\Resources\FeatureResource\Tables\FeatureTable;use Modules\Feature\Models\Feature;class FeatureResource extends Resource{protected static ?string $model = Feature::class;protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-squares-2x2';protected static ?int $navigationSort = 1;public static function form(Schema $schema): Schema{return FeatureForm::configure($schema);}public static function table(Table $table): Table{return FeatureTable::configure($table);}public static function getPages(): array{return ['index' => Pages\ListFeatures::route('/'),'create' => Pages\CreateFeature::route('/create'),'edit' => Pages\EditFeature::route('/{record}/edit'),];}}
Form Schema Pattern
namespace Modules\Feature\Filament\Resources\FeatureResource\Schemas;use Filament\Forms\Components\DateTimePicker;use Filament\Forms\Components\RichEditor;use Filament\Forms\Components\TextInput;use Filament\Forms\Components\Toggle;use Filament\Schemas\Components\Section;use Filament\Schemas\Schema;class FeatureForm{public static function configure(Schema $schema): Schema{return $schema->columns(2)->components([Section::make('Details')->schema([TextInput::make('name')->required()->maxLength(255)->columnSpanFull(),RichEditor::make('description')->nullable()->columnSpanFull(),Toggle::make('is_active')->default(true),DateTimePicker::make('starts_at')->nullable(),])->columns(2),]);}}
Use ->components([...]) (not ->schema([...])). Layout components (Grid, Section) come from Filament\Schemas\Components\.
Table Schema Pattern
namespace Modules\Feature\Filament\Resources\FeatureResource\Tables;use Filament\Actions\BulkActionGroup;use Filament\Actions\DeleteBulkAction;use Filament\Actions\EditAction;use Filament\Tables\Columns\IconColumn;use Filament\Tables\Columns\TextColumn;use Filament\Tables\Filters\TernaryFilter;use Filament\Tables\Table;class FeatureTable{public static function configure(Table $table): Table{return $table->columns([TextColumn::make('name')->searchable()->sortable(),IconColumn::make('is_active')->boolean(),TextColumn::make('created_at')->dateTime()->sortable()->toggleable(isToggledHiddenByDefault: true),])->filters([TernaryFilter::make('is_active'),])->recordActions([EditAction::make(),])->toolbarActions([BulkActionGroup::make([DeleteBulkAction::make(),]),]);}}
Filament v5 API: use ->recordActions() for row actions and ->toolbarActions() for bulk/header actions. Never use the old ->actions() / ->bulkActions().
Infolist Schema Pattern
Used for read-only view pages. Infolist entries come from Filament\Infolists\Components\.
namespace Modules\Feature\Filament\Resources\FeatureResource\Schemas;use Filament\Infolists\Components\IconEntry;use Filament\Infolists\Components\TextEntry;use Filament\Schemas\Components\Section;use Filament\Schemas\Schema;class FeatureInfolist{public static function configure(Schema $schema): Schema{return $schema->columns(2)->components([Section::make()->inlineLabel()->schema([TextEntry::make('name'),TextEntry::make('created_at')->dateTime(),IconEntry::make('is_active')->boolean()->trueColor('success')->falseColor('danger'),])->columnSpan(1),]);}}
Pages Pattern
List page:
namespace Modules\Feature\Filament\Resources\FeatureResource\Pages;use Filament\Actions\CreateAction;use Filament\Resources\Pages\ListRecords;use Modules\Feature\Filament\Resources\FeatureResource\FeatureResource;class ListFeatures extends ListRecords{protected static string $resource = FeatureResource::class;protected function getHeaderActions(): array{return [CreateAction::make()];}}
Create page (with creator tracking):
class CreateFeature extends CreateRecord{protected static string $resource = FeatureResource::class;/** @param array<string, mixed> $data @return array<string, mixed> */protected function mutateFormDataBeforeCreate(array $data): array{$data['created_by'] = auth()->id();return $data;}}
Edit page:
class EditFeature extends EditRecord{protected static string $resource = FeatureResource::class;protected function getHeaderActions(): array{return [DeleteAction::make()->requiresConfirmation(),];}}
Correct Namespaces
| Component type | Namespace | |
|---|---|---|
Form fields (TextInput, Select, Toggle, etc.) | Filament\Forms\Components\ | |
Infolist entries (TextEntry, IconEntry, etc.) | Filament\Infolists\Components\ | |
Layout (Grid, Section, Tabs, Wizard, Text) | Filament\Schemas\Components\ | |
Schema utilities (Get, Set) | Filament\Schemas\Components\Utilities\ | |
Actions (all — EditAction, DeleteAction, BulkActionGroup, etc.) | Filament\Actions\ | |
| Table columns | Filament\Tables\Columns\ | |
| Table filters | Filament\Tables\Filters\ | |
| Icons | Filament\Support\Icons\Heroicon enum |
Never use Filament\Tables\Actions\, Filament\Forms\Actions\, or other sub-namespaces for actions.
Testing
Always authenticate before testing panel functionality. Tests must be PHPUnit classes:
namespace Modules\Feature\Tests\Feature\Filament;use App\Models\User;use Illuminate\Foundation\Testing\RefreshDatabase;use Modules\Feature\Filament\Resources\FeatureResource\Pages\CreateFeature;use Modules\Feature\Filament\Resources\FeatureResource\Pages\ListFeatures;use Modules\Feature\Models\Feature;use Tests\TestCase;class FeatureResourceTest extends TestCase{use RefreshDatabase;protected function setUp(): void{parent::setUp();$this->actingAs(User::factory()->create()->assignRole('admin'));}public function test_can_list_features(): void{$features = Feature::factory()->count(3)->create();livewire(ListFeatures::class)->assertCanSeeTableRecords($features);}public function test_can_create_feature(): void{livewire(CreateFeature::class)->fillForm(['name' => 'Test Feature'])->call('create')->assertHasNoFormErrors();$this->assertDatabaseHas('features', ['name' => 'Test Feature']);}public function test_validates_required_fields(): void{livewire(CreateFeature::class)->fillForm(['name' => null])->call('create')->assertHasFormErrors(['name' => 'required']);}}
Run with:
php -d memory_limit=2048M artisan test --testsuite=Modules --filter='^Modules\Feature\Tests'
Common Mistakes
- Wrong namespace: module Filament classes live at
Modules\Feature\Filament\— neverModules\Feature\app\Filament\. - Old table API: use
->recordActions()/->toolbarActions(), never->actions()/->bulkActions(). - Action namespaces: always import from
Filament\Actions\, never from sub-namespaces. - Layout components:
Grid,Section,Tabscome fromFilament\Schemas\Components\, notFilament\Forms\Components\. - Plugin not discovered: class must be exactly at
Modules\{Name}\Filament\{Name}Plugin— casing must match the module folder name. - File visibility: file uploads are
privateby default — use->visibility('public')when public access is needed. - Column spans:
GridandSectiondo not span all columns by default — set->columnSpanFull()or->columnSpan(n)explicitly.