Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

)\n ->maxValue(fn (Order $record) => $record->total),\n \n Select::make('reason')\n ->options([\n 'customer_request' => 'Customer Request',\n 'damaged_item' => 'Damaged Item',\n 'wrong_item' => 'Wrong Item',\n 'other' => 'Other',\n ])\n ->required(),\n \n Textarea::make('notes')\n ->columnSpanFull(),\n ]),\n ])\n ->action(function (array $data, Order $record) {\n // Process refund\n $refund = $record->refunds()->create([\n 'amount' => $data['amount'],\n 'reason' => $data['reason'],\n 'notes' => $data['notes'],\n 'processed_by' => auth()->id(),\n ]);\n \n // Update order status\n $record->update([\n 'status' => 'refunded',\n 'refunded_amount' => $record->refunded_amount + $data['amount'],\n ]);\n \n // Send notification\n Notification::make()\n ->title('Refund processed')\n ->body(\"Refund #{$refund->id} for {$data['amount']} has been processed.\")\n ->success()\n ->actions([\n Notification\\Action::make('view')\n ->button()\n ->url(\"/admin/refunds/{$refund->id}\"),\n ])\n ->send();\n })\n ->visible(fn (Order $record): bool => \n $record->status === 'completed' && \n $record->refunded_amount \u003c $record->total\n )\n ->authorize(fn (Order $record): bool => \n auth()->user()->can('process refunds')\n )\n```\n\n## Import Action\n\n```php\nuse Filament\\Actions\\ImportAction;\nuse Filament\\Forms\\Components\\FileUpload;\n\nAction::make('import')\n ->icon('heroicon-m-arrow-up-tray')\n ->form([\n FileUpload::make('file')\n ->acceptedFileTypes([\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'text/csv',\n ])\n ->required(),\n ])\n ->action(function (array $data) {\n $import = new ProductsImport();\n Excel::import($import, $data['file']);\n \n Notification::make()\n ->title('Import complete')\n ->body(\"{$import->getRowCount()} products imported successfully.\")\n ->success()\n ->send();\n })\n```\n\n## Export Action\n\n```php\nuse Filament\\Actions\\ExportAction;\n\nAction::make('export')\n ->icon('heroicon-m-arrow-down-tray')\n ->form([\n Select::make('format')\n ->options([\n 'csv' => 'CSV',\n 'xlsx' => 'Excel',\n 'pdf' => 'PDF',\n ])\n ->default('csv'),\n \n Toggle::make('include_headers')\n ->default(true),\n ])\n ->action(function (array $data) {\n $format = $data['format'];\n $filename = \"products-{$format}.\" . ($format === 'xlsx' ? 'xlsx' : $format);\n \n return match ($format) {\n 'csv' => response()->streamDownload(fn () => Product::toCsv(), $filename),\n 'xlsx' => Excel::download(new ProductsExport(), $filename),\n 'pdf' => PDF::loadView('exports.products', ['products' => Product::all()])->download($filename),\n };\n })\n```\n\n## Best Practices\n\n1. **Use appropriate colors** - Danger for destructive, Success for positive\n2. **Always confirm destructive actions** - Use requiresConfirmation()\n3. **Provide clear labels** - Action should describe what it does\n4. **Use icons** - Enhance visual recognition\n5. **Handle errors gracefully** - Catch exceptions and show notifications\n6. **Authorize actions** - Check permissions before allowing\n7. **Give feedback** - Always send notifications after actions\n8. **Use action groups** - Group related actions together\n9. **Customize modals** - Set appropriate headings and descriptions\n10. **Optimize bulk actions** - Use database transactions for efficiency\n11. **Show loading states** - For long-running actions\n12. **Log important actions** - For audit trails\n13. **Test edge cases** - Empty data, large datasets, errors\n14. **Use success notifications** - Confirm actions completed\n15. **Keep actions focused** - One action should do one thing\n\n## Tips & Tricks\n\n### Action with Loading State\n\n```php\nAction::make('sync')\n ->action(function () {\n // Long-running operation\n $this->syncData();\n })\n ->requiresConfirmation()\n ->modalHeading('Sync Data')\n ->modalDescription('This may take a few minutes...')\n```\n\n### Dynamic Action Label\n\n```php\nAction::make('toggleStatus')\n ->label(fn (Post $record): string => $record->is_published ? 'Unpublish' : 'Publish')\n ->icon(fn (Post $record): string => $record->is_published ? 'heroicon-m-x-circle' : 'heroicon-m-check-circle')\n ->color(fn (Post $record): string => $record->is_published ? 'danger' : 'success')\n ->action(fn (Post $record) => $record->update(['is_published' => ! $record->is_published]))\n```\n\n### Conditional Confirmation\n\n```php\nAction::make('delete')\n ->requiresConfirmation(fn (Post $record): bool => $record->comments()->count() > 0)\n ->modalHeading('Delete Post')\n ->modalDescription(fn (Post $record): string => \n $record->comments()->count() > 0 \n ? \"This post has {$record->comments()->count()} comments. Are you sure?\"\n : 'Are you sure you want to delete this post?'\n )\n```\n\n## Additional Resources\n\n- [Official Actions Documentation](https://filamentphp.com/docs/5.x/actions/overview)\n- [Notifications](https://filamentphp.com/docs/5.x/notifications/overview)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17192,"content_sha256":"c85995c122f2bbcf4d6a7b9d2e763d31cc62e6f096c6e15bcc4b07abcf47e308"},{"filename":"references/authorization.md","content":"# Authorization Reference\n\nComplete guide for access control, policies, and multi-tenancy in Filament v5.\n\n## Panel Access Control\n\n### FilamentUser Contract\n\nImplement the `FilamentUser` contract on your User model to control panel access:\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse Filament\\Models\\Contracts\\FilamentUser;\nuse Filament\\Panel;\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\n\nclass User extends Authenticatable implements FilamentUser\n{\n // Simple check\n public function canAccessPanel(Panel $panel): bool\n {\n return $this->hasVerifiedEmail();\n }\n \n // Panel-specific checks\n public function canAccessPanel(Panel $panel): bool\n {\n return match ($panel->getId()) {\n 'admin' => $this->isAdmin() && $this->hasVerifiedEmail(),\n 'app' => $this->hasVerifiedEmail(),\n 'vendor' => $this->isVendor() && $this->hasVerifiedEmail(),\n default => false,\n };\n }\n \n // Domain-based access\n public function canAccessPanel(Panel $panel): bool\n {\n return str_ends_with($this->email, '@company.com') && \n $this->hasVerifiedEmail();\n }\n}\n```\n\n## Laravel Policies\n\nFilament automatically uses Laravel policies for authorization.\n\n### Creating Policies\n\n```bash\nphp artisan make:policy PostPolicy --model=Post\n```\n\n### Policy Structure\n\n```php\n\u003c?php\n\nnamespace App\\Policies;\n\nuse App\\Models\\Post;\nuse App\\Models\\User;\nuse Illuminate\\Auth\\Access\\Response;\n\nclass PostPolicy\n{\n public function viewAny(User $user): bool\n {\n return true;\n }\n\n public function view(User $user, Post $post): bool\n {\n return $user->id === $post->user_id || $user->isAdmin();\n }\n\n public function create(User $user): bool\n {\n return $user->can('create posts');\n }\n\n public function update(User $user, Post $post): bool|Response\n {\n if ($post->isLocked()) {\n return Response::deny('This post is locked and cannot be edited.');\n }\n\n return $user->id === $post->user_id || $user->isAdmin();\n }\n\n public function delete(User $user, Post $post): bool\n {\n return $user->isAdmin() || \n ($user->id === $post->user_id && $post->status === 'draft');\n }\n\n public function deleteAny(User $user): bool\n {\n return $user->isAdmin();\n }\n\n public function restore(User $user, Post $post): bool\n {\n return $user->isAdmin();\n }\n\n public function forceDelete(User $user, Post $post): bool\n {\n return $user->isAdmin();\n }\n \n // Custom policy methods\n public function publish(User $user, Post $post): bool\n {\n return $user->can('publish posts') && \n $post->status === 'draft';\n }\n \n public function feature(User $user, Post $post): bool\n {\n return $user->isAdmin() || $user->isEditor();\n }\n}\n```\n\n### Registering Policies\n\n```php\n\u003c?php\n\nnamespace App\\Providers;\n\nuse App\\Models\\Post;\nuse App\\Policies\\PostPolicy;\nuse Illuminate\\Foundation\\Support\\Providers\\AuthServiceProvider as ServiceProvider;\n\nclass AuthServiceProvider extends ServiceProvider\n{\n protected $policies = [\n Post::class => PostPolicy::class,\n ];\n\n public function boot(): void\n {\n //\n }\n}\n```\n\n## Resource Authorization\n\n### Automatic Policy Enforcement\n\nBy default, Filament checks policies automatically:\n\n```php\nclass PostResource extends Resource\n{\n protected static bool $shouldSkipAuthorization = false; // Default\n}\n```\n\n### Manual Authorization Methods\n\n```php\nclass PostResource extends Resource\n{\n public static function canViewAny(): bool\n {\n return auth()->user()->can('viewAny', Post::class);\n }\n\n public static function canCreate(): bool\n {\n return auth()->user()->can('create', Post::class);\n }\n\n public static function canEdit(Model $record): bool\n {\n return auth()->user()->can('update', $record);\n }\n\n public static function canDelete(Model $record): bool\n {\n return auth()->user()->can('delete', $record);\n }\n\n public static function canView(Model $record): bool\n {\n return auth()->user()->can('view', $record);\n }\n\n public static function canRestore(Model $record): bool\n {\n return auth()->user()->can('restore', $record);\n }\n\n public static function canForceDelete(Model $record): bool\n {\n return auth()->user()->can('forceDelete', $record);\n }\n}\n```\n\n### Eloquent Query Scoping\n\n```php\npublic static function getEloquentQuery(): Builder\n{\n return parent::getEloquentQuery()\n ->where(function (Builder $query) {\n if (! auth()->user()->isAdmin()) {\n $query->where('user_id', auth()->id());\n }\n });\n}\n```\n\n## Page Authorization\n\n### Custom Pages\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Pages;\n\nuse Filament\\Pages\\Page;\n\nclass Reports extends Page\n{\n protected static ?string $navigationIcon = 'heroicon-o-chart-bar';\n protected static string $view = 'filament.pages.reports';\n\n public static function canAccess(): bool\n {\n return auth()->user()->can('view reports');\n }\n \n // Or with dependencies\n public static function canAccess(array $parameters = []): bool\n {\n return auth()->user()->can('view reports') &&\n $parameters['type'] ?? false;\n }\n}\n```\n\n### Settings Pages\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Pages;\n\nuse Filament\\Pages\\Page;\n\nclass Settings extends Page\n{\n protected static ?string $navigationIcon = 'heroicon-o-cog';\n protected static string $view = 'filament.pages.settings';\n\n public static function canAccess(): bool\n {\n return auth()->user()->isAdmin();\n }\n}\n```\n\n## Action Authorization\n\n### Table Actions\n\n```php\n->actions([\n Tables\\Actions\\EditAction::make()\n ->authorize('update'),\n \n Tables\\Actions\\DeleteAction::make()\n ->authorize('delete'),\n \n Tables\\Actions\\Action::make('publish')\n ->icon('heroicon-m-check-circle')\n ->authorize('publish') // Calls PostPolicy::publish()\n ->visible(fn (Post $record): bool => \n auth()->user()->can('publish', $record)\n )\n ->action(fn (Post $record) => $record->update(['status' => 'published'])),\n])\n```\n\n### Page Actions\n\n```php\nprotected function getHeaderActions(): array\n{\n return [\n Action::make('export')\n ->icon('heroicon-m-arrow-down-tray')\n ->authorize('export posts')\n ->action(function () {\n // Export logic\n }),\n ];\n}\n```\n\n## Field-Level Authorization\n\n### Form Fields\n\n```php\npublic static function form(Form $form): Form\n{\n return $form\n ->schema([\n TextInput::make('title')\n ->required(),\n \n // Only visible to admins\n TextInput::make('internal_notes')\n ->visible(fn (): bool => auth()->user()->isAdmin()),\n \n // Only visible to users with permission\n TextInput::make('reviewer_notes')\n ->visible(fn (): bool => auth()->user()->can('review posts')),\n \n // Disabled for non-editors\n Select::make('status')\n ->disabled(fn (): bool => !auth()->user()->can('publish posts')),\n \n // Dehydrated (not saved) for certain roles\n TextInput::make('debug_info')\n ->dehydrated(fn (): bool => auth()->user()->isDeveloper()),\n ]);\n}\n```\n\n### Table Columns\n\n```php\npublic static function table(Table $table): Table\n{\n return $table\n ->columns([\n TextColumn::make('title'),\n \n // Only visible to admins\n TextColumn::make('internal_notes')\n ->visible(fn (): bool => auth()->user()->isAdmin()),\n \n // Toggleable but hidden by default for non-admins\n TextColumn::make('cost')\n ->money('USD')\n ->toggleable(\n isToggledHiddenByDefault: !auth()->user()->isAdmin()\n ),\n ]);\n}\n```\n\n## Multi-Tenancy\n\n### Tenant Setup\n\n```php\n\u003c?php\n\nnamespace App\\Providers\\Filament;\n\nuse App\\Models\\Team;\nuse Filament\\Panel;\nuse Filament\\PanelProvider;\n\nclass AdminPanelProvider extends PanelProvider\n{\n public function panel(Panel $panel): Panel\n {\n return $panel\n // ...\n ->tenant(Team::class)\n ->tenantRegistration(\\App\\Filament\\Pages\\Tenancy\\RegisterTeam::class)\n ->tenantProfile(\\App\\Filament\\Pages\\Tenancy\\EditTeamProfile::class)\n ->tenantMenuItems([\n \\Filament\\Actions\\Action::make('settings')\n ->url(fn (): string => TeamSettings::getUrl())\n ->icon('heroicon-m-cog-8-tooth'),\n ]);\n }\n}\n```\n\n### User Model with Tenancy\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse Filament\\Models\\Contracts\\FilamentUser;\nuse Filament\\Models\\Contracts\\HasDefaultTenant;\nuse Filament\\Models\\Contracts\\HasTenants;\nuse Filament\\Panel;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\nuse Illuminate\\Support\\Collection;\n\nclass User extends Authenticatable implements FilamentUser, HasTenants, HasDefaultTenant\n{\n public function teams(): BelongsToMany\n {\n return $this->belongsToMany(Team::class)\n ->withTimestamps()\n ->withPivot('role');\n }\n\n public function getTenants(Panel $panel): Collection\n {\n return $this->teams;\n }\n\n public function canAccessTenant(Model $tenant): bool\n {\n return $this->teams()\n ->whereKey($tenant->getKey())\n ->exists();\n }\n\n public function getDefaultTenant(Panel $panel): ?Model\n {\n return $this->latestTeam;\n }\n\n public function latestTeam()\n {\n return $this->belongsTo(Team::class, 'latest_team_id');\n }\n}\n```\n\n### Tenant Registration Page\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Pages\\Tenancy;\n\nuse App\\Models\\Team;\nuse Filament\\Forms\\Components\\TextInput;\nuse Filament\\Forms\\Form;\nuse Filament\\Pages\\Tenancy\\RegisterTenant;\nuse Filament\\Schemas\\Schema;\n\nclass RegisterTeam extends RegisterTenant\n{\n public static function getLabel(): string\n {\n return 'Register team';\n }\n\n public function form(Schema $schema): Schema\n {\n return $schema\n ->components([\n TextInput::make('name')\n ->required()\n ->maxLength(255),\n TextInput::make('slug')\n ->required()\n ->unique('teams', 'slug')\n ->maxLength(255),\n ]);\n }\n\n protected function handleRegistration(array $data): Team\n {\n $team = Team::create($data);\n $team->members()->attach(auth()->user(), ['role' => 'owner']);\n return $team;\n }\n}\n```\n\n### Tenant Profile Page\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Pages\\Tenancy;\n\nuse Filament\\Forms\\Components\\TextInput;\nuse Filament\\Forms\\Form;\nuse Filament\\Pages\\Tenancy\\EditTenantProfile;\nuse Filament\\Schemas\\Schema;\n\nclass EditTeamProfile extends EditTenantProfile\n{\n public static function getLabel(): string\n {\n return 'Team profile';\n }\n\n public function form(Schema $schema): Schema\n {\n return $schema\n ->components([\n TextInput::make('name')\n ->required()\n ->maxLength(255),\n TextInput::make('slug')\n ->required()\n ->unique('teams', 'slug', ignoreRecord: true)\n ->maxLength(255),\n ]);\n }\n}\n```\n\n### Tenant Subdomain Routing\n\n```php\npublic function panel(Panel $panel): Panel\n{\n return $panel\n // ...\n ->tenant(Team::class, slugAttribute: 'slug')\n ->tenantDomain('{tenant:slug}.example.com');\n}\n```\n\n### Tenant Path Routing\n\n```php\npublic function panel(Panel $panel): Panel\n{\n return $panel\n // ...\n ->tenant(Team::class, slugAttribute: 'slug')\n ->tenantRoutePrefix('{tenant:slug}');\n}\n```\n\n### Tenant-Aware Resources\n\n```php\nclass PostResource extends Resource\n{\n public static function getEloquentQuery(): Builder\n {\n return parent::getEloquentQuery()\n ->whereBelongsTo(Filament::getTenant());\n }\n}\n```\n\nOr use a global scope:\n\n```php\nclass Post extends Model\n{\n protected static function booted(): void\n {\n static::addGlobalScope('tenant', function (Builder $query) {\n if (auth()->hasUser() && Filament::getTenant()) {\n $query->whereBelongsTo(Filament::getTenant());\n }\n });\n \n static::creating(function (Post $post) {\n if (Filament::getTenant()) {\n $post->team()->associate(Filament::getTenant());\n }\n });\n }\n}\n```\n\n## Role-Based Access Control\n\n### Using Spatie Laravel-Permission\n\n```bash\ncomposer require spatie/laravel-permission\nphp artisan vendor:publish --provider=\"Spatie\\Permission\\PermissionServiceProvider\"\n```\n\n### User Model\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse Filament\\Models\\Contracts\\FilamentUser;\nuse Filament\\Panel;\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\nuse Spatie\\Permission\\Traits\\HasRoles;\n\nclass User extends Authenticatable implements FilamentUser\n{\n use HasRoles;\n\n public function canAccessPanel(Panel $panel): bool\n {\n return $this->hasAnyRole(['admin', 'editor', 'author']);\n }\n \n public function isAdmin(): bool\n {\n return $this->hasRole('admin');\n }\n \n public function isEditor(): bool\n {\n return $this->hasRole('editor');\n }\n}\n```\n\n### Resource with Role Checks\n\n```php\nclass PostResource extends Resource\n{\n public static function canCreate(): bool\n {\n return auth()->user()->hasAnyRole(['admin', 'editor', 'author']);\n }\n\n public static function canEdit(Model $record): bool\n {\n $user = auth()->user();\n \n if ($user->hasRole('admin')) {\n return true;\n }\n \n if ($user->hasRole('editor')) {\n return true;\n }\n \n return $user->id === $record->user_id;\n }\n\n public static function canDelete(Model $record): bool\n {\n return auth()->user()->hasRole('admin');\n }\n}\n```\n\n### Permission-Based Checks\n\n```php\npublic static function canPublish(): bool\n{\n return auth()->user()->can('publish posts');\n}\n\npublic static function canFeature(): bool\n{\n return auth()->user()->can('feature posts');\n}\n```\n\n## Navigation Visibility\n\n### Resource Navigation\n\n```php\nclass PostResource extends Resource\n{\n // Hide from navigation entirely\n protected static bool $shouldRegisterNavigation = false;\n \n // Dynamic visibility\n public static function shouldRegisterNavigation(): bool\n {\n return auth()->user()->can('view posts');\n }\n}\n```\n\n### Page Navigation\n\n```php\nclass Reports extends Page\n{\n public static function shouldRegisterNavigation(): bool\n {\n return auth()->user()->can('view reports');\n }\n}\n```\n\n## Advanced Authorization\n\n### Custom Authorization Logic\n\n```php\nclass PostResource extends Resource\n{\n public static function canEdit(Model $record): bool\n {\n $user = auth()->user();\n \n // Admin can edit anything\n if ($user->isAdmin()) {\n return true;\n }\n \n // Owner can edit their own posts\n if ($user->id === $record->user_id) {\n // But not if locked\n if ($record->isLocked()) {\n return false;\n }\n \n // And not if published (only drafts editable by owner)\n if ($record->status === 'published') {\n return false;\n }\n \n return true;\n }\n \n // Editors can edit any draft\n if ($user->isEditor() && $record->status === 'draft') {\n return true;\n }\n \n return false;\n }\n}\n```\n\n### Time-Based Restrictions\n\n```php\npublic static function canEdit(Model $record): bool\n{\n // Can't edit posts older than 30 days (unless admin)\n if ($record->created_at->diffInDays(now()) > 30 && !auth()->user()->isAdmin()) {\n return false;\n }\n \n return auth()->user()->can('update', $record);\n}\n```\n\n### Feature Flags\n\n```php\npublic static function canCreate(): bool\n{\n // Check if feature is enabled\n if (!Feature::active('new-posts')) {\n return false;\n }\n \n return auth()->user()->can('create posts');\n}\n```\n\n## Best Practices\n\n1. **Use policies for model-level authorization**\n2. **Implement FilamentUser for panel access**\n3. **Check permissions in actions, not just visibility**\n4. **Use custom policy methods for specific actions**\n5. **Implement multi-tenancy at the query level**\n6. **Cache expensive permission checks**\n7. **Test authorization thoroughly**\n8. **Use roles for broad access, permissions for specific actions**\n9. **Keep authorization logic in policies, not controllers**\n10. **Use Response::deny() with messages for clarity**\n11. **Document your authorization rules**\n12. **Regularly audit permissions**\n13. **Use middleware for route-level protection**\n14. **Implement proper tenant isolation**\n15. **Log sensitive authorization failures**\n\n## Testing Authorization\n\n```php\nuse function Pest\\Laravel\\actingAs;\n\nit('denies access to guests', function () {\n auth()->logout();\n\n livewire(ListPosts::class)\n ->assertRedirect('/login');\n});\n\nit('denies access to unauthorized users', function () {\n $user = User::factory()->create(['role' => 'user']);\n actingAs($user);\n\n livewire(ListPosts::class)\n ->assertForbidden();\n});\n\nit('allows access to authorized users', function () {\n $admin = User::factory()->create(['role' => 'admin']);\n actingAs($admin);\n\n livewire(ListPosts::class)\n ->assertOk();\n});\n\nit('hides actions without permission', function () {\n $user = User::factory()->create();\n $user->revokePermissionTo('delete posts');\n actingAs($user);\n\n $post = Post::factory()->create();\n\n livewire(ListPosts::class)\n ->assertTableActionHidden('delete', $post);\n});\n```\n\n## Additional Resources\n\n- [Laravel Authorization](https://laravel.com/docs/authorization)\n- [Spatie Laravel-Permission](https://spatie.be/docs/laravel-permission)\n- [Filament Multi-Tenancy](https://filamentphp.com/docs/5.x/panels/tenancy)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":18680,"content_sha256":"958c608a117d7fc2af9f970b0f05b9fa05d7cb724ddcd0700329e311bf4dfa58"},{"filename":"references/examples.md","content":"# Filament v5 Code Examples\n\nComplete working code examples for Filament v5 components.\n\n## Panel Provider\n\n### Basic Configuration\n\n```php\n\u003c?php\n\nnamespace App\\Providers\\Filament;\n\nuse Filament\\Panel;\nuse Filament\\PanelProvider;\nuse Filament\\Support\\Colors\\Color;\n\nclass AdminPanelProvider extends PanelProvider\n{\n public function panel(Panel $panel): Panel\n {\n return $panel\n ->id('admin')\n ->path('admin')\n ->colors(['primary' => Color::Amber])\n ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')\n ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')\n ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')\n ->middleware([\n \\Illuminate\\Cookie\\Middleware\\EncryptCookies::class,\n \\Illuminate\\Session\\Middleware\\StartSession::class,\n ])\n ->authMiddleware([\n \\Illuminate\\Auth\\Middleware\\Authenticate::class,\n ]);\n }\n}\n```\n\n### With Branding\n\n```php\npublic function panel(Panel $panel): Panel\n{\n return $panel\n ->id('admin')\n ->path('admin')\n ->brandName('My Admin')\n ->brandLogo(asset('images/logo.svg'))\n ->favicon(asset('images/favicon.ico'))\n ->colors([\n 'primary' => '#f59e0b',\n 'secondary' => '#64748b',\n 'success' => '#22c55e',\n 'warning' => '#f59e0b',\n 'danger' => '#ef4444',\n ]);\n}\n```\n\n## Resources\n\n### Complete Post Resource\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources;\n\nuse App\\Filament\\Resources\\PostResource\\Pages;\nuse App\\Models\\Post;\nuse Filament\\Forms;\nuse Filament\\Forms\\Form;\nuse Filament\\Resources\\Resource;\nuse Filament\\Tables;\nuse Filament\\Tables\\Table;\n\nclass PostResource extends Resource\n{\n protected static ?string $model = Post::class;\n protected static ?string $navigationIcon = 'heroicon-o-document-text';\n protected static ?string $navigationGroup = 'Content';\n\n public static function form(Form $form): Form\n {\n return $form\n ->schema([\n Forms\\Components\\TextInput::make('title')\n ->required()\n ->maxLength(255)\n ->live(onBlur: true)\n ->afterStateUpdated(fn ($state, $set) => \n $set('slug', \\Illuminate\\Support\\Str::slug($state))\n ),\n \n Forms\\Components\\TextInput::make('slug')\n ->required()\n ->unique(ignoreRecord: true)\n ->maxLength(255),\n \n Forms\\Components\\RichEditor::make('content')\n ->required()\n ->columnSpanFull(),\n \n Forms\\Components\\Select::make('status')\n ->options([\n 'draft' => 'Draft',\n 'published' => 'Published',\n 'archived' => 'Archived',\n ])\n ->required(),\n \n Forms\\Components\\DateTimePicker::make('published_at'),\n \n Forms\\Components\\Toggle::make('is_featured'),\n \n Forms\\Components\\Select::make('author_id')\n ->relationship('author', 'name')\n ->searchable()\n ->preload()\n ->required(),\n \n Forms\\Components\\Select::make('categories')\n ->relationship('categories', 'name')\n ->multiple()\n ->preload()\n ->searchable(),\n ])\n ->columns(2);\n }\n\n public static function table(Table $table): Table\n {\n return $table\n ->columns([\n Tables\\Columns\\TextColumn::make('title')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('status')\n ->badge()\n ->color(fn (string $state): string => match ($state) {\n 'draft' => 'gray',\n 'published' => 'success',\n 'archived' => 'warning',\n }),\n \n Tables\\Columns\\TextColumn::make('author.name')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\IconColumn::make('is_featured')\n ->boolean(),\n \n Tables\\Columns\\TextColumn::make('created_at')\n ->dateTime()\n ->sortable(),\n ])\n ->filters([\n Tables\\Filters\\SelectFilter::make('status')\n ->options([\n 'draft' => 'Draft',\n 'published' => 'Published',\n 'archived' => 'Archived',\n ]),\n Tables\\Filters\\TernaryFilter::make('is_featured'),\n ])\n ->actions([\n Tables\\Actions\\ViewAction::make(),\n Tables\\Actions\\EditAction::make(),\n Tables\\Actions\\DeleteAction::make(),\n ])\n ->bulkActions([\n Tables\\Actions\\BulkActionGroup::make([\n Tables\\Actions\\DeleteBulkAction::make(),\n ]),\n ]);\n }\n\n public static function getPages(): array\n {\n return [\n 'index' => Pages\\ListPosts::class,\n 'create' => Pages\\CreatePost::class,\n 'view' => Pages\\ViewPost::class,\n 'edit' => Pages\\EditPost::class,\n ];\n }\n}\n```\n\n## Forms\n\n### User Registration Form\n\n```php\nuse Filament\\Forms\\Components\\TextInput;\nuse Filament\\Forms\\Components\\Select;\nuse Filament\\Forms\\Components\\DatePicker;\nuse Filament\\Forms\\Components\\FileUpload;\nuse Filament\\Forms\\Components\\Toggle;\nuse Filament\\Forms\\Components\\Section;\nuse Filament\\Forms\\Components\\Grid;\n\npublic static function form(Form $form): Form\n{\n return $form\n ->schema([\n Section::make('Personal Information')\n ->schema([\n TextInput::make('first_name')\n ->required()\n ->maxLength(255),\n TextInput::make('last_name')\n ->required()\n ->maxLength(255),\n TextInput::make('email')\n ->email()\n ->required()\n ->unique(ignoreRecord: true),\n DatePicker::make('birthdate')\n ->maxDate(now()->subYears(18)),\n ])\n ->columns(2),\n \n Section::make('Account Settings')\n ->schema([\n TextInput::make('password')\n ->password()\n ->required()\n ->minLength(8)\n ->confirmed(),\n TextInput::make('password_confirmation')\n ->password()\n ->required(),\n Select::make('role')\n ->options([\n 'admin' => 'Administrator',\n 'editor' => 'Editor',\n 'user' => 'User',\n ])\n ->required(),\n Toggle::make('is_active')\n ->default(true),\n ])\n ->columns(2),\n \n Section::make('Profile')\n ->schema([\n FileUpload::make('avatar')\n ->image()\n ->circleCropper()\n ->maxSize(5120),\n ]),\n ]);\n}\n```\n\n### Product Form with Repeater\n\n```php\nuse Filament\\Forms\\Components\\Repeater;\nuse Filament\\Forms\\Components\\TextInput;\nuse Filament\\Forms\\Components\\Select;\n\npublic static function form(Form $form): Form\n{\n return $form\n ->schema([\n TextInput::make('name')\n ->required(),\n \n Repeater::make('variants')\n ->schema([\n Select::make('size')\n ->options(['S' => 'Small', 'M' => 'Medium', 'L' => 'Large'])\n ->required(),\n TextInput::make('sku')\n ->required(),\n TextInput::make('price')\n ->numeric()\n ->prefix('

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

)\n ->required(),\n TextInput::make('stock')\n ->numeric()\n ->required(),\n ])\n ->columns(4)\n ->defaultItems(1)\n ->addActionLabel('Add Variant')\n ->reorderable(),\n ]);\n}\n```\n\n### Settings Form with Tabs\n\n```php\nuse Filament\\Forms\\Components\\Tabs;\nuse Filament\\Forms\\Components\\TextInput;\nuse Filament\\Forms\\Components\\Textarea;\n\npublic static function form(Form $form): Form\n{\n return $form\n ->schema([\n Tabs::make('Settings')\n ->tabs([\n Tabs\\Tab::make('General')\n ->schema([\n TextInput::make('site_name')\n ->required(),\n TextInput::make('site_email')\n ->email()\n ->required(),\n ]),\n Tabs\\Tab::make('SEO')\n ->schema([\n TextInput::make('meta_title'),\n Textarea::make('meta_description')\n ->maxLength(160),\n ]),\n Tabs\\Tab::make('Social')\n ->schema([\n TextInput::make('facebook_url'),\n TextInput::make('twitter_url'),\n ]),\n ]),\n ]);\n}\n```\n\n## Tables\n\n### Product Catalog Table\n\n```php\npublic static function table(Table $table): Table\n{\n return $table\n ->columns([\n Tables\\Columns\\ImageColumn::make('image')\n ->square()\n ->size(50),\n \n Tables\\Columns\\TextColumn::make('name')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('category.name')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('price')\n ->money('USD')\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('stock_quantity')\n ->numeric()\n ->sortable()\n ->color(fn (int $state): string => match (true) {\n $state \u003c= 0 => 'danger',\n $state \u003c= 10 => 'warning',\n default => 'success',\n }),\n \n Tables\\Columns\\IconColumn::make('is_active')\n ->boolean(),\n ])\n ->filters([\n Tables\\Filters\\SelectFilter::make('category')\n ->relationship('category', 'name'),\n \n Tables\\Filters\\Filter::make('low_stock')\n ->query(fn ($query) => $query->where('stock_quantity', '\u003c=', 10))\n ->toggle(),\n ])\n ->actions([\n Tables\\Actions\\EditAction::make(),\n Tables\\Actions\\DeleteAction::make(),\n ])\n ->bulkActions([\n Tables\\Actions\\BulkActionGroup::make([\n Tables\\Actions\\DeleteBulkAction::make(),\n ]),\n ]);\n}\n```\n\n## Actions\n\n### Email Action with Modal\n\n```php\nuse Filament\\Actions\\Action;\nuse Filament\\Notifications\\Notification;\n\nAction::make('sendEmail')\n ->icon('heroicon-m-envelope')\n ->form([\n TextInput::make('subject')\n ->required()\n ->maxLength(255),\n RichEditor::make('body')\n ->required()\n ->columnSpanFull(),\n ])\n ->action(function (array $data, $record): void {\n Mail::to($record->email)\n ->send(new GenericEmail($data['subject'], $data['body']));\n \n Notification::make()\n ->title('Email sent successfully')\n ->success()\n ->send();\n })\n ->modalWidth(MaxWidth::Large);\n```\n\n### Delete with Confirmation\n\n```php\nAction::make('delete')\n ->color('danger')\n ->icon('heroicon-m-trash')\n ->requiresConfirmation()\n ->modalHeading('Delete record')\n ->modalDescription('Are you sure? This cannot be undone.')\n ->action(fn ($record) => $record->delete());\n```\n\n## Widgets\n\n### Stats Overview\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Widgets;\n\nuse App\\Models\\Order;\nuse App\\Models\\User;\nuse Filament\\Widgets\\StatsOverviewWidget;\nuse Filament\\Widgets\\StatsOverviewWidget\\Stat;\n\nclass DashboardStats extends StatsOverviewWidget\n{\n protected function getStats(): array\n {\n return [\n Stat::make('Total Users', User::count())\n ->description(User::where('created_at', '>=', now()->subDays(30))->count() . ' new this month')\n ->color('success'),\n \n Stat::make('Revenue', '

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

. number_format(Order::sum('total'), 2))\n ->description('Total sales')\n ->color('primary'),\n \n Stat::make('Pending Orders', Order::where('status', 'pending')->count())\n ->color('warning'),\n ];\n }\n}\n```\n\n### Chart Widget\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Widgets;\n\nuse App\\Models\\Order;\nuse Filament\\Widgets\\ChartWidget;\n\nclass OrdersChart extends ChartWidget\n{\n protected ?string $heading = 'Orders per Month';\n \n protected function getData(): array\n {\n $orders = Order::query()\n ->selectRaw('MONTH(created_at) as month, COUNT(*) as count')\n ->whereYear('created_at', now()->year)\n ->groupBy('month')\n ->pluck('count', 'month')\n ->toArray();\n\n return [\n 'datasets' => [\n [\n 'label' => 'Orders',\n 'data' => array_values($orders),\n ],\n ],\n 'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', \n 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],\n ];\n }\n\n protected function getType(): string\n {\n return 'line';\n }\n}\n```\n\n## Testing\n\n### Resource Tests\n\n```php\n\u003c?php\n\nuse App\\Filament\\Resources\\PostResource\\Pages\\CreatePost;\nuse App\\Filament\\Resources\\PostResource\\Pages\\EditPost;\nuse App\\Filament\\Resources\\PostResource\\Pages\\ListPosts;\nuse App\\Models\\Post;\nuse App\\Models\\User;\n\nuse function Pest\\Laravel\\actingAs;\nuse function Pest\\Livewire\\livewire;\n\nbeforeEach(function () {\n $this->user = User::factory()->create();\n actingAs($this->user);\n});\n\n// List Page\nit('can list posts', function () {\n $posts = Post::factory()->count(5)->create();\n \n livewire(ListPosts::class)\n ->assertCanSeeTableRecords($posts);\n});\n\nit('can search posts', function () {\n Post::factory()->create(['title' => 'Hello World']);\n \n livewire(ListPosts::class)\n ->searchTable('Hello')\n ->assertCanSeeTableRecords(Post::where('title', 'like', '%Hello%')->get());\n});\n\n// Create Page\nit('can create a post', function () {\n $newData = Post::factory()->make();\n \n livewire(CreatePost::class)\n ->fillForm([\n 'title' => $newData->title,\n 'content' => $newData->content,\n ])\n ->call('create')\n ->assertNotified()\n ->assertRedirect();\n});\n\nit('validates required fields', function () {\n livewire(CreatePost::class)\n ->fillForm(['title' => null])\n ->call('create')\n ->assertHasFormErrors(['title']);\n});\n\n// Edit Page\nit('can update a post', function () {\n $post = Post::factory()->create();\n $newData = Post::factory()->make();\n \n livewire(EditPost::class, ['record' => $post->id])\n ->fillForm(['title' => $newData->title])\n ->call('save')\n ->assertNotified();\n});\n```\n\n## Authorization\n\n### User Model with Panel Access\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse Filament\\Models\\Contracts\\FilamentUser;\nuse Filament\\Panel;\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\n\nclass User extends Authenticatable implements FilamentUser\n{\n public function canAccessPanel(Panel $panel): bool\n {\n return match ($panel->getId()) {\n 'admin' => $this->isAdmin(),\n 'app' => $this->hasVerifiedEmail(),\n default => false,\n };\n }\n}\n```\n\n### Policy\n\n```php\n\u003c?php\n\nnamespace App\\Policies;\n\nuse App\\Models\\Post;\nuse App\\Models\\User;\n\nclass PostPolicy\n{\n public function viewAny(User $user): bool\n {\n return true;\n }\n\n public function view(User $user, Post $post): bool\n {\n return $user->id === $post->user_id || $user->isAdmin();\n }\n\n public function create(User $user): bool\n {\n return $user->can('create posts');\n }\n\n public function update(User $user, Post $post): bool\n {\n return $user->id === $post->user_id || $user->isAdmin();\n }\n\n public function delete(User $user, Post $post): bool\n {\n return $user->isAdmin();\n }\n}\n```\n\n### Resource with Authorization\n\n```php\nclass PostResource extends Resource\n{\n public static function canCreate(): bool\n {\n return auth()->user()->can('create posts');\n }\n \n public static function canEdit($record): bool\n {\n return auth()->user()->can('update', $record);\n }\n \n public static function form(Form $form): Form\n {\n return $form\n ->schema([\n TextInput::make('title')->required(),\n \n // Only visible to admins\n TextInput::make('internal_notes')\n ->visible(fn (): bool => auth()->user()->isAdmin()),\n ]);\n }\n}\n```\n\n## Multi-Tenancy\n\n### User with Tenancy\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse Filament\\Models\\Contracts\\HasTenants;\nuse Filament\\Panel;\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany;\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\nuse Illuminate\\Support\\Collection;\n\nclass User extends Authenticatable implements HasTenants\n{\n public function teams(): BelongsToMany\n {\n return $this->belongsToMany(Team::class);\n }\n \n public function getTenants(Panel $panel): Collection\n {\n return $this->teams;\n }\n \n public function canAccessTenant(Model $tenant): bool\n {\n return $this->teams()->whereKey($tenant)->exists();\n }\n}\n```\n\n### Panel with Tenancy\n\n```php\npublic function panel(Panel $panel): Panel\n{\n return $panel\n ->tenant(Team::class)\n ->tenantProfile(\\App\\Filament\\Pages\\TeamProfile::class)\n ->tenantRegistration(\\App\\Filament\\Pages\\TeamRegistration::class);\n}\n```\n\n## Custom Pages\n\n### Settings Page\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Pages;\n\nuse Filament\\Actions\\Action;\nuse Filament\\Forms\\Components\\TextInput;\nuse Filament\\Forms\\Form;\nuse Filament\\Notifications\\Notification;\nuse Filament\\Pages\\Page;\n\nclass Settings extends Page\n{\n protected static ?string $navigationIcon = 'heroicon-o-cog';\n protected static ?string $navigationGroup = 'System';\n protected static string $view = 'filament.pages.settings';\n \n public ?array $data = [];\n \n public function mount(): void\n {\n $this->form->fill([\n 'site_name' => config('app.name'),\n ]);\n }\n \n public static function canAccess(): bool\n {\n return auth()->user()->isAdmin();\n }\n \n public function form(Form $form): Form\n {\n return $form\n ->schema([\n TextInput::make('site_name')->required(),\n ])\n ->statePath('data');\n }\n \n protected function getHeaderActions(): array\n {\n return [\n Action::make('save')\n ->action('save'),\n ];\n }\n \n public function save(): void\n {\n $data = $this->form->getState();\n setting(['site_name' => $data['site_name']]);\n \n Notification::make()\n ->title('Settings saved')\n ->success()\n ->send();\n }\n}\n```\n\n## Relation Managers\n\n### Comments Relation Manager\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources\\PostResource\\RelationManagers;\n\nuse Filament\\Forms;\nuse Filament\\Forms\\Form;\nuse Filament\\Resources\\RelationManagers\\RelationManager;\nuse Filament\\Tables;\nuse Filament\\Tables\\Table;\n\nclass CommentsRelationManager extends RelationManager\n{\n protected static string $relationship = 'comments';\n \n public function form(Form $form): Form\n {\n return $form\n ->schema([\n Forms\\Components\\Textarea::make('content')\n ->required()\n ->columnSpanFull(),\n ]);\n }\n \n public function table(Table $table): Table\n {\n return $table\n ->columns([\n Tables\\Columns\\TextColumn::make('content')->limit(50),\n Tables\\Columns\\TextColumn::make('user.name'),\n ])\n ->headerActions([\n Tables\\Actions\\CreateAction::make(),\n ])\n ->actions([\n Tables\\Actions\\EditAction::make(),\n Tables\\Actions\\DeleteAction::make(),\n ]);\n }\n}\n```\n\n## Complete Blog Example\n\n### Category Resource\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources;\n\nuse App\\Models\\Category;\nuse Filament\\Forms;\nuse Filament\\Forms\\Form;\nuse Filament\\Resources\\Resource;\nuse Filament\\Tables;\nuse Filament\\Tables\\Table;\nuse Illuminate\\Support\\Str;\n\nclass CategoryResource extends Resource\n{\n protected static ?string $model = Category::class;\n protected static ?string $navigationIcon = 'heroicon-o-tag';\n protected static ?string $navigationGroup = 'Blog';\n \n public static function form(Form $form): Form\n {\n return $form\n ->schema([\n Forms\\Components\\TextInput::make('name')\n ->required()\n ->live(onBlur: true)\n ->afterStateUpdated(fn ($state, $set) => \n $set('slug', Str::slug($state))\n ),\n \n Forms\\Components\\TextInput::make('slug')\n ->required()\n ->unique(ignoreRecord: true),\n \n Forms\\Components\\ColorPicker::make('color'),\n ]);\n }\n \n public static function table(Table $table): Table\n {\n return $table\n ->columns([\n Tables\\Columns\\ColorColumn::make('color'),\n Tables\\Columns\\TextColumn::make('name')->searchable(),\n Tables\\Columns\\TextColumn::make('posts_count')->counts('posts'),\n ]);\n }\n}\n```\n\n### Post Resource with Relations\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources;\n\nuse App\\Models\\Post;\nuse Filament\\Forms;\nuse Filament\\Forms\\Form;\nuse Filament\\Resources\\Resource;\nuse Filament\\Tables;\nuse Filament\\Tables\\Table;\n\nclass PostResource extends Resource\n{\n protected static ?string $model = Post::class;\n protected static ?string $navigationIcon = 'heroicon-o-document-text';\n protected static ?string $navigationGroup = 'Blog';\n \n public static function form(Form $form): Form\n {\n return $form\n ->schema([\n Forms\\Components\\TextInput::make('title')->required(),\n \n Forms\\Components\\RichEditor::make('content')\n ->required()\n ->columnSpanFull(),\n \n Forms\\Components\\Select::make('category_id')\n ->relationship('category', 'name')\n ->searchable(),\n \n Forms\\Components\\Select::make('tags')\n ->relationship('tags', 'name')\n ->multiple()\n ->preload(),\n \n Forms\\Components\\Toggle::make('is_published'),\n ])\n ->columns(2);\n }\n \n public static function table(Table $table): Table\n {\n return $table\n ->columns([\n Tables\\Columns\\TextColumn::make('title')->searchable(),\n Tables\\Columns\\TextColumn::make('category.name'),\n Tables\\Columns\\IconColumn::make('is_published')->boolean(),\n ]);\n }\n \n public static function getRelations(): array\n {\n return [\n RelationManagers\\CommentsRelationManager::class,\n ];\n }\n}\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":25171,"content_sha256":"81811b4e97ea0c9c25c454534ba645b3ab9f8e36e3330fe181d284a0dc1f8c69"},{"filename":"references/forms.md","content":"# Form Components Reference\n\nComplete reference for Filament v5 form components and schemas.\n\n## Available Form Fields\n\nFilament v5 provides 20+ form field types in the `Filament\\Forms\\Components` namespace:\n\n| Field Type | Class | Description |\n|------------|-------|-------------|\n| Text Input | `TextInput` | Single-line text input with validation |\n| Textarea | `Textarea` | Multi-line text input |\n| Select | `Select` | Dropdown selection |\n| Checkbox | `Checkbox` | Boolean checkbox |\n| Toggle | `Toggle` | Switch toggle |\n| Checkbox List | `CheckboxList` | Multiple checkboxes |\n| Radio | `Radio` | Radio button group |\n| Date Picker | `DatePicker` | Date selection |\n| DateTime Picker | `DateTimePicker` | Date and time selection |\n| File Upload | `FileUpload` | File upload with preview |\n| Rich Editor | `RichEditor` | WYSIWYG editor (TipTap) |\n| Markdown Editor | `MarkdownEditor` | Markdown editing |\n| Repeater | `Repeater` | Repeatable field groups |\n| Builder | `Builder` | Block-based content builder |\n| Tags Input | `TagsInput` | Tag creation |\n| Key-value | `KeyValue` | Key-value pairs |\n| Color Picker | `ColorPicker` | Color selection |\n| Toggle Buttons | `ToggleButtons` | Button group selection |\n| Slider | `Slider` | Range slider |\n| Hidden | `Hidden` | Hidden input field |\n\n## Text Input\n\n```php\nuse Filament\\Forms\\Components\\TextInput;\n\nTextInput::make('name')\n ->required()\n ->maxLength(255)\n ->minLength(2)\n ->autocomplete()\n ->autofocus()\n ->placeholder('Enter name')\n ->prefix('Mr/Ms')\n ->suffix('@company.com')\n ->helperText('Your full name')\n ->hint('Required')\n ->hintIcon('heroicon-m-question-mark-circle')\n ->disabled()\n ->readonly()\n ->hidden()\n ->dehydrated(false) // Don't save to database\n ->live() // Real-time updates\n ->afterStateUpdated(fn ($state, $set) => $set('slug', Str::slug($state)))\n```\n\n### Validation Methods\n\n```php\nTextInput::make('email')\n ->email()\n ->required()\n ->unique('users', 'email', ignoreRecord: true)\n ->maxLength(255)\n \nTextInput::make('password')\n ->password()\n ->required()\n ->minLength(8)\n ->regex('/^(?=.*[A-Z])(?=.*\\d).+$/')\n ->confirmed() // Requires password_confirmation field\n \nTextInput::make('slug')\n ->required()\n ->alphaDash()\n ->unique('posts', 'slug', ignoreRecord: true)\n```\n\n## Select\n\n```php\nuse Filament\\Forms\\Components\\Select;\n\nSelect::make('status')\n ->options([\n 'draft' => 'Draft',\n 'published' => 'Published',\n 'archived' => 'Archived',\n ])\n ->required()\n ->searchable()\n ->preload()\n ->multiple()\n ->native(false)\n ->placeholder('Select a status')\n ->noSearchResultsMessage('No status found')\n ->loadingMessage('Loading statuses...')\n ->searchPrompt('Search for a status')\n```\n\n### Relationship Selection\n\n```php\nSelect::make('author_id')\n ->relationship('author', 'name')\n ->searchable()\n ->preload()\n ->createOptionForm([\n TextInput::make('name')->required(),\n TextInput::make('email')->email()->required(),\n ])\n ->editOptionForm([\n TextInput::make('name')->required(),\n ])\n ->createOptionAction(fn ($data, $set) => $set('author_id', $data['id']))\n```\n\n### Dynamic Options\n\n```php\nSelect::make('city')\n ->options(fn (Get $get): array => match ($get('country')) {\n 'usa' => ['nyc' => 'New York', 'la' => 'Los Angeles'],\n 'uk' => ['london' => 'London', 'manchester' => 'Manchester'],\n default => [],\n })\n ->live()\n ->afterStateUpdated(fn (Set $set) => $set('zip', null))\n```\n\n## Checkbox & Toggle\n\n```php\nuse Filament\\Forms\\Components\\Checkbox;\nuse Filament\\Forms\\Components\\Toggle;\n\nCheckbox::make('is_active')\n ->label('Active')\n ->helperText('Check to activate')\n ->inline()\n \nToggle::make('is_featured')\n ->label('Featured')\n ->onColor('success')\n ->offColor('danger')\n ->inline(false)\n```\n\n## Date Pickers\n\n```php\nuse Filament\\Forms\\Components\\DatePicker;\nuse Filament\\Forms\\Components\\DateTimePicker;\nuse Filament\\Forms\\Components\\TimePicker;\n\nDatePicker::make('birthdate')\n ->required()\n ->minDate(now()->subYears(100))\n ->maxDate(now()->subYears(18))\n ->native(false)\n ->displayFormat('M d, Y')\n \nDateTimePicker::make('published_at')\n ->required()\n ->seconds(false)\n ->timezone('America/New_York')\n \nTimePicker::make('opening_time')\n ->required()\n ->withoutSeconds()\n```\n\n## File Upload\n\n```php\nuse Filament\\Forms\\Components\\FileUpload;\nuse Filament\\Forms\\Components\\ImageUpload;\n\nFileUpload::make('attachment')\n ->required()\n ->multiple()\n ->directory('attachments')\n ->disk('s3')\n ->preserveFilenames()\n ->maxSize(10240) // 10MB\n ->minSize(1024) // 1MB\n ->acceptedFileTypes(['application/pdf', 'image/*'])\n ->maxFiles(5)\n ->openable()\n ->downloadable()\n ->previewable()\n ->imagePreviewHeight('250')\n \nImageUpload::make('avatar')\n ->image()\n ->imageEditor()\n ->imageEditorAspectRatios([\n null,\n '16:9',\n '4:3',\n '1:1',\n ])\n ->circleCropper()\n ->squareCropper()\n```\n\n## Rich Editor\n\n```php\nuse Filament\\Forms\\Components\\RichEditor;\n\nRichEditor::make('content')\n ->required()\n ->columnSpanFull()\n ->toolbarButtons([\n 'attachFiles',\n 'blockquote',\n 'bold',\n 'bulletList',\n 'codeBlock',\n 'h2',\n 'h3',\n 'italic',\n 'link',\n 'orderedList',\n 'redo',\n 'strike',\n 'underline',\n 'undo',\n ])\n ->fileAttachmentsDirectory('posts/images')\n ->fileAttachmentsDisk('s3')\n```\n\n## Repeater\n\n```php\nuse Filament\\Forms\\Components\\Repeater;\n\nRepeater::make('items')\n ->schema([\n TextInput::make('name')->required(),\n TextInput::make('quantity')->numeric()->required(),\n TextInput::make('price')->numeric()->prefix('

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

)->required(),\n ])\n ->columns(3)\n ->defaultItems(1)\n ->addActionLabel('Add Item')\n ->reorderable(true)\n ->collapsible()\n ->collapsed()\n ->cloneable()\n ->grid(2)\n ->maxItems(10)\n ->minItems(1)\n ->deleteAction(\n fn (Action $action) => $action->requiresConfirmation(),\n )\n```\n\n## Builder (Block Editor)\n\n```php\nuse Filament\\Forms\\Components\\Builder;\n\nBuilder::make('content')\n ->blocks([\n Builder\\Block::make('heading')\n ->schema([\n TextInput::make('content')\n ->label('Heading')\n ->required(),\n Select::make('level')\n ->options([\n 'h1' => 'Heading 1',\n 'h2' => 'Heading 2',\n 'h3' => 'Heading 3',\n ])\n ->required(),\n ])\n ->label('Heading'),\n \n Builder\\Block::make('paragraph')\n ->schema([\n RichEditor::make('content')\n ->label('Paragraph')\n ->required(),\n ])\n ->label('Paragraph'),\n \n Builder\\Block::make('image')\n ->schema([\n FileUpload::make('image')\n ->image()\n ->required(),\n TextInput::make('caption'),\n ])\n ->label('Image'),\n ])\n ->collapsible()\n ->defaultItems(0)\n```\n\n## Layout Components\n\n### Grid\n\n```php\nuse Filament\\Forms\\Components\\Grid;\n\nGrid::make(2)\n ->schema([\n TextInput::make('first_name'),\n TextInput::make('last_name'),\n ])\n\n// Responsive grid\nGrid::make([\n 'default' => 1,\n 'sm' => 2,\n 'lg' => 3,\n 'xl' => 4,\n])->schema([\n // ...\n])\n```\n\n### Section\n\n```php\nuse Filament\\Forms\\Components\\Section;\n\nSection::make('Personal Information')\n ->description('Enter your personal details')\n ->icon('heroicon-m-user')\n ->collapsible()\n ->collapsed()\n ->compact()\n ->aside() // Side-by-side layout\n ->schema([\n TextInput::make('name'),\n TextInput::make('email'),\n ])\n ->columns(2)\n```\n\n### Tabs\n\n```php\nuse Filament\\Forms\\Components\\Tabs;\n\nTabs::make('Settings')\n ->tabs([\n Tabs\\Tab::make('General')\n ->icon('heroicon-m-cog')\n ->schema([\n TextInput::make('site_name'),\n TextInput::make('site_email'),\n ]),\n \n Tabs\\Tab::make('SEO')\n ->icon('heroicon-m-globe')\n ->schema([\n TextInput::make('meta_title'),\n Textarea::make('meta_description'),\n ]),\n \n Tabs\\Tab::make('Social')\n ->icon('heroicon-m-share')\n ->schema([\n TextInput::make('facebook_url'),\n TextInput::make('twitter_url'),\n ]),\n ])\n```\n\n### Wizard\n\n```php\nuse Filament\\Forms\\Components\\Wizard;\n\nWizard::make([\n Wizard\\Step::make('Account')\n ->icon('heroicon-m-user')\n ->description('Create your account')\n ->schema([\n TextInput::make('email')\n ->email()\n ->required(),\n TextInput::make('password')\n ->password()\n ->required()\n ->confirmed(),\n ]),\n \n Wizard\\Step::make('Profile')\n ->icon('heroicon-m-identification')\n ->description('Set up your profile')\n ->schema([\n TextInput::make('name')->required(),\n FileUpload::make('avatar')->image(),\n ]),\n \n Wizard\\Step::make('Preferences')\n ->icon('heroicon-m-cog')\n ->description('Customize your experience')\n ->schema([\n Toggle::make('newsletter'),\n Select::make('timezone'),\n ]),\n])\n->skippable()\n->persistInQueryString()\n->submitAction(\n Action::make('createAccount')\n ->label('Create Account')\n)\n```\n\n## Fieldset\n\n```php\nuse Filament\\Forms\\Components\\Fieldset;\n\nFieldset::make('Address')\n ->schema([\n TextInput::make('street'),\n TextInput::make('city'),\n TextInput::make('zip'),\n ])\n```\n\n## Groups\n\n```php\nuse Filament\\Forms\\Components\\Group;\n\nGroup::make()\n ->schema([\n // Fields that should be grouped together\n ])\n ->columnSpanFull()\n ->columns(2)\n```\n\n## Placeholder\n\n```php\nuse Filament\\Forms\\Components\\Placeholder;\n\nPlaceholder::make('summary')\n ->content(fn ($record): string => \"Created: {$record->created_at}\")\n ->columnSpanFull()\n```\n\n## KeyValue\n\n```php\nuse Filament\\Forms\\Components\\KeyValue;\n\nKeyValue::make('meta')\n ->keyLabel('Property')\n ->valueLabel('Value')\n ->addActionLabel('Add Property')\n ->keyPlaceholder('Property name')\n ->valuePlaceholder('Property value')\n ->reorderable()\n```\n\n## Tags Input\n\n```php\nuse Filament\\Forms\\Components\\TagsInput;\n\nTagsInput::make('skills')\n ->label('Skills')\n ->placeholder('Add a skill')\n ->suggestions(['PHP', 'Laravel', 'JavaScript', 'Vue.js', 'React'])\n ->splitKeys(['Tab', ',', 'Enter'])\n ->reorderable()\n```\n\n## Color Picker\n\n```php\nuse Filament\\Forms\\Components\\ColorPicker;\n\nColorPicker::make('color')\n ->label('Brand Color')\n ->hex()\n ->rgb()\n ->rgba()\n ->hsl()\n ->hsv()\n ->default('#f59e0b')\n```\n\n## Toggle Buttons\n\n```php\nuse Filament\\Forms\\Components\\ToggleButtons;\n\nToggleButtons::make('size')\n ->options([\n 'sm' => 'Small',\n 'md' => 'Medium',\n 'lg' => 'Large',\n ])\n ->icons([\n 'sm' => 'heroicon-m-sun',\n 'md' => 'heroicon-m-moon',\n 'lg' => 'heroicon-m-star',\n ])\n ->default('md')\n ->inline()\n ->grouped()\n```\n\n## Slider\n\n```php\nuse Filament\\Forms\\Components\\Slider;\n\nSlider::make('volume')\n ->min(0)\n ->max(100)\n ->step(10)\n ->default(50)\n ->suffix('%')\n```\n\n## Validation\n\n### Built-in Rules\n\n```php\nTextInput::make('email')\n ->required()\n ->email()\n ->unique('users', 'email', ignoreRecord: true)\n ->maxLength(255)\n ->minLength(5)\n\nTextInput::make('password')\n ->required()\n ->minLength(8)\n ->regex('/^(?=.*[A-Z])(?=.*\\d).+$/')\n ->confirmed() // Requires password_confirmation\n\nTextInput::make('age')\n ->numeric()\n ->minValue(18)\n ->maxValue(100)\n ->integer()\n\nTextInput::make('website')\n ->url()\n ->activeUrl()\n\nTextInput::make('ip')\n ->ip()\n\nTextInput::make('mac')\n ->macAddress()\n```\n\n### Custom Rules\n\n```php\nTextInput::make('username')\n ->rules(['required', 'string', 'min:3', 'max:20', 'regex:/^[a-zA-Z0-9_]+$/'])\n \nTextInput::make('code')\n ->rule(function ($state) {\n return $state === 'valid-code' ? null : 'Invalid code';\n })\n```\n\n### Conditional Rules\n\n```php\nTextInput::make('company_name')\n ->required(fn (Get $get): bool => $get('is_company'))\n \nTextInput::make('phone')\n ->requiredWithout('email')\n```\n\n## Conditional Visibility\n\n```php\nTextInput::make('company_name')\n ->visible(fn (Get $get): bool => $get('is_company'))\n ->hidden(fn (Get $get): bool => ! $get('is_company'))\n ->disabled(fn (Get $get): bool => $get('is_locked'))\n ->readonly(fn (): bool => auth()->user()->cannot('edit'))\n```\n\n## State Management\n\n```php\nuse Filament\\Forms\\Components\\Utilities\\Get;\nuse Filament\\Forms\\Components\\Utilities\\Set;\n\nSelect::make('country')\n ->live()\n ->afterStateUpdated(fn (Set $set) => $set('city', null))\n\nTextInput::make('name')\n ->live(onBlur: true)\n ->afterStateUpdated(fn ($state, Set $set) => $set('slug', Str::slug($state)))\n\n// Access other field values\nTextInput::make('total')\n ->formatStateUsing(fn ($state, Get $get): string => \n $get('quantity') * $get('price')\n )\n ->dehydrated(false)\n```\n\n## Complete Example: Product Form\n\n```php\npublic static function form(Form $form): Form\n{\n return $form\n ->schema([\n Section::make('Basic Information')\n ->schema([\n TextInput::make('name')\n ->required()\n ->maxLength(255)\n ->live(onBlur: true)\n ->afterStateUpdated(fn ($state, $set) => \n $set('slug', Str::slug($state))\n ),\n \n TextInput::make('slug')\n ->required()\n ->unique(ignoreRecord: true)\n ->disabled()\n ->dehydrated(),\n \n Select::make('category_id')\n ->relationship('category', 'name')\n ->searchable()\n ->preload()\n ->required(),\n \n Textarea::make('description')\n ->required()\n ->maxLength(1000)\n ->columnSpanFull(),\n ])\n ->columns(2),\n \n Section::make('Pricing & Inventory')\n ->schema([\n TextInput::make('price')\n ->required()\n ->numeric()\n ->prefix('

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

)\n ->maxValue(999999.99),\n \n TextInput::make('sale_price')\n ->numeric()\n ->prefix('

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

)\n ->lte('price', 'Must be less than or equal to regular price'),\n \n TextInput::make('stock_quantity')\n ->required()\n ->numeric()\n ->integer()\n ->minValue(0),\n \n Toggle::make('track_inventory')\n ->default(true),\n ])\n ->columns(2),\n \n Section::make('Media')\n ->schema([\n FileUpload::make('images')\n ->multiple()\n ->image()\n ->maxFiles(10)\n ->directory('products')\n ->columnSpanFull(),\n ]),\n \n Section::make('SEO')\n ->schema([\n TextInput::make('meta_title')\n ->maxLength(70),\n \n Textarea::make('meta_description')\n ->maxLength(160)\n ->columnSpanFull(),\n ])\n ->collapsible()\n ->collapsed(),\n ]);\n}\n```\n\n## Tips & Best Practices\n\n1. **Use sections to group related fields**\n2. **Leverage live() for real-time updates**\n3. **Always validate input with built-in methods**\n4. **Use afterStateUpdated to compute derived fields**\n5. **Disable fields that shouldn't be edited instead of hiding them**\n6. **Use placeholders for computed/display-only values**\n7. **Enable reorderable() on repeaters for better UX**\n8. **Use builder blocks for flexible content structures**\n9. **Add helper text and hints for complex fields**\n10. **Test forms with various input combinations**\n\n## Additional Resources\n\n- [Official Forms Documentation](https://filamentphp.com/docs/5.x/schemas/forms)\n- [Form Layout](https://filamentphp.com/docs/5.x/schemas/layout)\n- [Validation](https://filamentphp.com/docs/5.x/schemas/validation)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":17436,"content_sha256":"e470cbafb585e239d874247c3ef0b7a2484dcfd016b37df1af1af4be5f77bf46"},{"filename":"references/infolists.md","content":"# Infolists Reference\n\nComplete guide for creating read-only data displays in Filament v5.\n\n## Overview\n\nInfolists are components for rendering \"description lists\" - read-only displays of data in a label-value format. They are commonly used on view pages, custom pages, and relation managers to present record information.\n\n## Basic Infolist\n\n### Simple Infolist\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources\\PostResource;\n\nuse Filament\\Infolists\\Components\\TextEntry;\nuse Filament\\Infolists\\Infolist;\nuse Filament\\Resources\\Resource;\n\nclass PostResource extends Resource\n{\n public static function infolist(Infolist $infolist): Infolist\n {\n return $infolist\n ->schema([\n TextEntry::make('title'),\n TextEntry::make('content')\n ->columnSpanFull(),\n TextEntry::make('created_at')\n ->dateTime(),\n ]);\n }\n}\n```\n\n## Entry Types\n\n### Text Entry\n\n```php\nuse Filament\\Infolists\\Components\\TextEntry;\n\nTextEntry::make('name')\n ->label('Full Name')\n ->weight('bold') // 'bold', 'semibold', 'medium'\n ->color('primary') // 'primary', 'success', 'danger', etc.\n ->icon('heroicon-m-user')\n ->iconColor('primary')\n ->copyable()\n ->copyMessage('Copied!')\n ->copyMessageDuration(1500)\n ->limit(50)\n ->tooltip('Full name of the user')\n ->placeholder('No name provided')\n ->prefix('Mr/Ms ')\n ->suffix(' (verified)')\n ->alignLeft() // alignLeft, alignCenter, alignRight\n ->columnSpanFull()\n ->hidden(fn ($record) => !$record->name)\n ->visible(fn ($record) => $record->isPublished());\n```\n\n### Text Formatting\n\n```php\nTextEntry::make('price')\n ->money('USD') // Currency formatting\n ->formatStateUsing(fn ($state) => number_format($state, 2));\n\nTextEntry::make('created_at')\n ->dateTime('M j, Y g:i A') // Custom date format\n ->since(); // Relative time (\"2 hours ago\")\n\nTextEntry::make('content')\n ->markdown() // Render as markdown\n ->html() // Render as HTML\n ->prose() // Apply prose styling\n ->columnSpanFull();\n\nTextEntry::make('slug')\n ->url(fn ($record) => route('posts.show', $record))\n ->openUrlInNewTab();\n\nTextEntry::make('status')\n ->badge()\n ->color(fn (string $state): string => match ($state) {\n 'draft' => 'gray',\n 'published' => 'success',\n 'archived' => 'danger',\n });\n```\n\n### Icon Entry\n\n```php\nuse Filament\\Infolists\\Components\\IconEntry;\n\nIconEntry::make('is_active')\n ->boolean() // Shows check/x icons\n ->trueIcon('heroicon-m-check-circle')\n ->falseIcon('heroicon-m-x-circle')\n ->trueColor('success')\n ->falseColor('danger');\n\nIconEntry::make('status')\n ->icon(fn (string $state): string => match ($state) {\n 'active' => 'heroicon-m-check-circle',\n 'inactive' => 'heroicon-m-x-circle',\n default => 'heroicon-m-question-mark-circle',\n })\n ->color(fn (string $state): string => match ($state) {\n 'active' => 'success',\n 'inactive' => 'danger',\n default => 'gray',\n });\n```\n\n### Image Entry\n\n```php\nuse Filament\\Infolists\\Components\\ImageEntry;\n\nImageEntry::make('avatar')\n ->disk('public')\n ->square() // Square aspect ratio\n ->circular() // Circular (avatar style)\n ->size(100) // Size in pixels\n ->width(200)\n ->height(200)\n ->checkFileExistence()\n ->defaultImageUrl(fn ($record) => 'https://ui-avatars.com/api/?name=' . urlencode($record->name));\n\nImageEntry::make('gallery')\n ->multiple()\n ->limit(3) // Show only 3 images\n ->limitedRemainingText(); // Show \"+X more\" text\n```\n\n### Color Entry\n\n```php\nuse Filament\\Infolists\\Components\\ColorEntry;\n\nColorEntry::make('brand_color')\n ->copyable();\n```\n\n### Key-Value Entry\n\n```php\nuse Filament\\Infolists\\Components\\KeyValueEntry;\n\nKeyValueEntry::make('meta')\n ->keyLabel('Property')\n ->valueLabel('Value');\n```\n\n### Repeatable Entry\n\n```php\nuse Filament\\Infolists\\Components\\RepeatableEntry;\nuse Filament\\Infolists\\Components\\TextEntry;\n\nRepeatableEntry::make('orderItems')\n ->schema([\n TextEntry::make('product.name'),\n TextEntry::make('quantity'),\n TextEntry::make('price')\n ->money('USD'),\n ])\n ->columns(3)\n ->contained(false); // Remove card styling\n```\n\n## Layout Components\n\n### Section\n\n```php\nuse Filament\\Infolists\\Components\\Section;\n\nSection::make('Personal Information')\n ->description('User details and contact information')\n ->icon('heroicon-m-user')\n ->collapsible()\n ->collapsed()\n ->compact()\n ->aside() // Side-by-side label/value\n ->schema([\n TextEntry::make('name'),\n TextEntry::make('email'),\n TextEntry::make('phone'),\n ])\n ->columns(2);\n```\n\n### Grid\n\n```php\nuse Filament\\Infolists\\Components\\Grid;\n\nGrid::make(2)\n ->schema([\n TextEntry::make('first_name'),\n TextEntry::make('last_name'),\n ]);\n\n// Responsive grid\nGrid::make([\n 'default' => 1,\n 'sm' => 2,\n 'lg' => 3,\n])\n ->schema([\n // ...\n ]);\n```\n\n### Tabs\n\n```php\nuse Filament\\Infolists\\Components\\Tabs;\n\nTabs::make('User Details')\n ->tabs([\n Tabs\\Tab::make('General')\n ->icon('heroicon-m-user')\n ->schema([\n TextEntry::make('name'),\n TextEntry::make('email'),\n ]),\n \n Tabs\\Tab::make('Professional')\n ->icon('heroicon-m-briefcase')\n ->schema([\n TextEntry::make('job_title'),\n TextEntry::make('company'),\n ]),\n \n Tabs\\Tab::make('Social')\n ->icon('heroicon-m-share')\n ->schema([\n TextEntry::make('twitter'),\n TextEntry::make('linkedin'),\n ]),\n ]);\n```\n\n### Split\n\n```php\nuse Filament\\Infolists\\Components\\Split;\n\nSplit::make([\n Section::make('Details')\n ->schema([\n TextEntry::make('name'),\n TextEntry::make('email'),\n ]),\n \n Section::make('Avatar')\n ->schema([\n ImageEntry::make('avatar')\n ->circular()\n ->size(150),\n ]),\n])\n ->from('lg'); // Split on large screens\n```\n\n### Fieldset\n\n```php\nuse Filament\\Infolists\\Components\\Fieldset;\n\nFieldset::make('Address')\n ->schema([\n TextEntry::make('street'),\n TextEntry::make('city'),\n TextEntry::make('zip'),\n ]);\n```\n\n## Complete Examples\n\n### User Profile Infolist\n\n```php\npublic static function infolist(Infolist $infolist): Infolist\n{\n return $infolist\n ->schema([\n Section::make('Profile')\n ->schema([\n Split::make([\n Grid::make(2)\n ->schema([\n TextEntry::make('name')\n ->weight('bold')\n ->size(TextEntry\\Size::Large),\n \n TextEntry::make('email')\n ->icon('heroicon-m-envelope')\n ->copyable(),\n \n TextEntry::make('phone')\n ->icon('heroicon-m-phone'),\n \n TextEntry::make('role.name')\n ->badge()\n ->color('primary'),\n \n IconEntry::make('is_active')\n ->boolean()\n ->label('Status'),\n ]),\n \n ImageEntry::make('avatar')\n ->circular()\n ->size(150)\n ->hiddenLabel(),\n ])\n ->from('md'),\n ]),\n \n Section::make('Professional Information')\n ->collapsible()\n ->schema([\n TextEntry::make('job_title'),\n TextEntry::make('company'),\n TextEntry::make('bio')\n ->markdown()\n ->columnSpanFull(),\n ])\n ->columns(2),\n \n Section::make('Metadata')\n ->collapsed()\n ->schema([\n TextEntry::make('created_at')\n ->dateTime()\n ->since(),\n \n TextEntry::make('updated_at')\n ->dateTime()\n ->since(),\n ])\n ->columns(2),\n ]);\n}\n```\n\n### Order Details Infolist\n\n```php\npublic static function infolist(Infolist $infolist): Infolist\n{\n return $infolist\n ->schema([\n Section::make('Order Summary')\n ->schema([\n TextEntry::make('order_number')\n ->label('Order #')\n ->weight('bold'),\n \n TextEntry::make('status')\n ->badge()\n ->color(fn (string $state): string => match ($state) {\n 'pending' => 'warning',\n 'processing' => 'info',\n 'completed' => 'success',\n 'cancelled' => 'danger',\n }),\n \n TextEntry::make('total')\n ->money('USD')\n ->weight('bold')\n ->size(TextEntry\\Size::Large),\n \n TextEntry::make('created_at')\n ->dateTime(),\n ])\n ->columns(4),\n \n Section::make('Customer')\n ->schema([\n TextEntry::make('customer.name')\n ->label('Name'),\n \n TextEntry::make('customer.email')\n ->label('Email')\n ->copyable(),\n \n TextEntry::make('customer.phone')\n ->label('Phone'),\n ])\n ->columns(3),\n \n Section::make('Order Items')\n ->schema([\n RepeatableEntry::make('items')\n ->hiddenLabel()\n ->schema([\n TextEntry::make('product.name')\n ->label('Product'),\n \n TextEntry::make('quantity')\n ->label('Qty'),\n \n TextEntry::make('price')\n ->label('Price')\n ->money('USD'),\n \n TextEntry::make('total')\n ->label('Total')\n ->money('USD')\n ->weight('bold'),\n ])\n ->columns(4)\n ->contained(false),\n ]),\n ]);\n}\n```\n\n## Advanced Features\n\n### Entry Groups\n\n```php\nuse Filament\\Infolists\\Components\\EntryGroup;\n\nSection::make('Contact Information')\n ->schema([\n EntryGroup::make([\n TextEntry::make('email')\n ->icon('heroicon-m-envelope'),\n TextEntry::make('phone')\n ->icon('heroicon-m-phone'),\n TextEntry::make('website')\n ->icon('heroicon-m-globe')\n ->url()\n ->openUrlInNewTab(),\n ])\n ->label('Contact Details')\n ->inlineLabel(),\n ]);\n```\n\n### Custom Entries\n\n```php\nuse Filament\\Infolists\\Components\\Entry;\n\nEntry::make('custom')\n ->label('Custom Data')\n ->view('filament.infolists.entries.custom')\n ->viewData([\n 'extra' => 'data',\n ]);\n```\n\n### Relationship Data\n\n```php\nTextEntry::make('author.name')\n ->label('Author')\n ->url(fn ($record) => UserResource::getUrl('view', ['record' => $record->author]))\n ->openUrlInNewTab();\n\nTextEntry::make('tags.name')\n ->badge()\n ->separator(',');\n```\n\n### Conditional Visibility\n\n```php\nSection::make('Premium Features')\n ->visible(fn ($record) => $record->isPremium())\n ->schema([\n TextEntry::make('premium_feature_1'),\n TextEntry::make('premium_feature_2'),\n ]);\n\nTextEntry::make('admin_notes')\n ->visible(fn () => auth()->user()->isAdmin());\n```\n\n### State Formatting\n\n```php\nTextEntry::make('price')\n ->formatStateUsing(fn ($state) => '

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

. number_format($state, 2));\n\nTextEntry::make('tags')\n ->formatStateUsing(function ($state) {\n return collect($state)->pluck('name')->join(', ');\n });\n\nTextEntry::make('status')\n ->formatStateUsing(fn ($state) => ucfirst($state));\n```\n\n## Using Infolists in Resources\n\n### Resource Infolist\n\n```php\nclass PostResource extends Resource\n{\n public static function infolist(Infolist $infolist): Infolist\n {\n return $infolist\n ->schema([\n // ... entries\n ]);\n }\n \n public static function getPages(): array\n {\n return [\n 'index' => Pages\\ListPosts::class,\n 'create' => Pages\\CreatePost::class,\n 'view' => Pages\\ViewPost::class, // Uses infolist\n 'edit' => Pages\\EditPost::class,\n ];\n }\n}\n```\n\n### Custom Page Infolist\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Pages;\n\nuse Filament\\Infolists\\Components\\TextEntry;\nuse Filament\\Infolists\\Infolist;\nuse Filament\\Pages\\Page;\n\nclass Dashboard extends Page\n{\n protected static string $view = 'filament.pages.dashboard';\n \n public ?array $data = [];\n \n public function mount(): void\n {\n $this->data = [\n 'total_users' => User::count(),\n 'total_orders' => Order::count(),\n ];\n }\n \n public function infolist(Infolist $infolist): Infolist\n {\n return $infolist\n ->state($this->data)\n ->schema([\n TextEntry::make('total_users')\n ->label('Total Users'),\n TextEntry::make('total_orders')\n ->label('Total Orders'),\n ]);\n }\n}\n```\n\n### Infolist in Actions\n\n```php\nAction::make('viewDetails')\n ->infolist([\n TextEntry::make('name'),\n TextEntry::make('email'),\n ])\n ->record($user)\n ->modalWidth(MaxWidth::Large);\n```\n\n## Best Practices\n\n1. **Use sections to group related data** - Organize logically\n2. **Leverage icons** - Enhance visual recognition\n3. **Format dates consistently** - Use `since()` or custom formats\n4. **Copyable fields** - Enable for emails, IDs, URLs\n5. **Use badges for statuses** - Visual state indicators\n6. **Collapsible sections** - For less important data\n7. **Responsive layouts** - Use responsive grids\n8. **Hide empty fields** - Use `hidden()` or `placeholder()`\n9. **Link related resources** - Connect to other pages\n10. **Keep it scannable** - Use bold labels, clear hierarchy\n\n## Additional Resources\n\n- [Official Infolists Documentation](https://filamentphp.com/docs/5.x/infolists)\n- [Entry Components](https://filamentphp.com/docs/5.x/infolists/entries)\n- [Layout Components](https://filamentphp.com/docs/5.x/infolists/layout)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":16062,"content_sha256":"1475878c5fccfa351793f7b2ef0ff54472270f3a1c3436e2c7cb47b008e6d6ee"},{"filename":"references/notifications.md","content":"# Notifications Reference\n\nComplete guide for sending notifications in Filament v5.\n\n## Overview\n\nFilament provides a powerful notification system for sending flash messages, database notifications, and broadcast notifications. Notifications can include actions, custom styling, and can be sent to specific users.\n\n## Basic Notifications\n\n### Simple Notifications\n\n```php\nuse Filament\\Notifications\\Notification;\n\n// Success notification\nNotification::make()\n ->title('Saved successfully')\n ->success()\n ->send();\n\n// Error notification\nNotification::make()\n ->title('Error occurred')\n ->body('Unable to save the record.')\n ->danger()\n ->send();\n\n// Warning notification\nNotification::make()\n ->title('Warning')\n ->body('This action cannot be undone.')\n ->warning()\n ->send();\n\n// Info notification\nNotification::make()\n ->title('Information')\n ->body('New updates are available.')\n ->info()\n ->send();\n```\n\n### Notification Types\n\n| Method | Color | Use Case |\n|--------|-------|----------|\n| `success()` | Green | Successful operations |\n| `danger()` | Red | Errors and failures |\n| `warning()` | Yellow | Warnings and cautions |\n| `info()` | Blue | Informational messages |\n| `secondary()` | Gray | Neutral messages |\n\n### Notification Properties\n\n```php\nNotification::make()\n ->title('Title here') // Main heading\n ->body('Description here') // Detailed message\n ->icon('heroicon-o-check') // Custom icon\n ->iconColor('success') // Icon color\n ->duration(5000) // Auto-dismiss after ms (default: 5000)\n ->persistent() // Don't auto-dismiss\n ->send();\n```\n\n## Notifications with Actions\n\n### Basic Actions\n\n```php\nuse Filament\\Actions\\Action;\n\nNotification::make()\n ->title('Order placed successfully')\n ->success()\n ->body('Your order #12345 has been confirmed.')\n ->actions([\n Action::make('view')\n ->button()\n ->url(route('orders.show', $order))\n ->openUrlInNewTab(),\n \n Action::make('undo')\n ->color('gray')\n ->close()\n ->action(function () {\n // Undo logic\n }),\n ])\n ->send();\n```\n\n### Action Types\n\n```php\nNotification::make()\n ->actions([\n // Button style action\n Action::make('view')\n ->button()\n ->url('/orders/123'),\n \n // Link style action\n Action::make('dismiss')\n ->link()\n ->close(),\n \n // Action with dispatch\n Action::make('refresh')\n ->dispatch('refreshData'),\n \n // Action with color\n Action::make('delete')\n ->color('danger')\n ->requiresConfirmation()\n ->action(fn () => $record->delete()),\n ])\n ->send();\n```\n\n## Database Notifications\n\n### Sending to Database\n\nDatabase notifications are stored and displayed in the notification center:\n\n```php\n// Send to specific user\nNotification::make()\n ->title('New comment on your post')\n ->body('John Doe commented on \"My First Post\"')\n ->actions([\n Action::make('view')\n ->url(route('posts.show', $post)),\n ])\n ->sendToDatabase($user);\n\n// Send to multiple users\n$users = User::where('role', 'admin')->get();\n\nNotification::make()\n ->title('System maintenance scheduled')\n ->body('Maintenance will occur tonight at 2 AM.')\n ->sendToDatabase($users);\n```\n\n### Marking as Read\n\n```php\n// User marks notification as read\n$user->notifications()->find($notificationId)->markAsRead();\n\n// Mark all as read\n$user->unreadNotifications->markAsRead();\n```\n\n## Broadcast Notifications\n\n### Broadcasting to Users\n\nRequires Laravel Echo and a broadcast driver (Pusher, Ably, etc.):\n\n```php\n// Send broadcast notification\nNotification::make()\n ->title('New order received')\n ->body('Order #12345 needs processing.')\n ->broadcast($user);\n```\n\n### Using in Laravel Notifications\n\n```php\nuse App\\Models\\User;\nuse Filament\\Notifications\\Notification;\nuse Illuminate\\Notifications\\Messages\\BroadcastMessage;\n\nclass OrderReceived extends Notification\n{\n public function toBroadcast(User $notifiable): BroadcastMessage\n {\n return Notification::make()\n ->title('New order received')\n ->body('Order #12345 needs processing.')\n ->getBroadcastMessage();\n }\n \n public function via($notifiable): array\n {\n return ['broadcast', 'database'];\n }\n}\n```\n\n## Notification in Actions\n\n### After Action Completion\n\n```php\nuse Filament\\Actions\\Action;\nuse Filament\\Notifications\\Notification;\n\nAction::make('publish')\n ->action(function (Post $record) {\n $record->update(['status' => 'published']);\n \n Notification::make()\n ->title('Post published')\n ->success()\n ->send();\n })\n ->successNotification(\n Notification::make()\n ->title('Published successfully')\n ->success()\n );\n```\n\n### Custom Notification in Actions\n\n```php\nAction::make('process')\n ->action(function () {\n try {\n $this->processData();\n \n Notification::make()\n ->title('Processing complete')\n ->success()\n ->send();\n } catch (Exception $e) {\n Notification::make()\n ->title('Processing failed')\n ->body($e->getMessage())\n ->danger()\n ->persistent()\n ->send();\n }\n });\n```\n\n## Notification Center\n\n### Enabling Notification Center\n\nAdd to your PanelProvider:\n\n```php\nuse Filament\\Notifications\\Livewire\\DatabaseNotifications;\n\npublic function panel(Panel $panel): Panel\n{\n return $panel\n // ...\n ->databaseNotifications()\n ->databaseNotificationsPolling('30s');\n}\n```\n\n### Customizing Notification Center\n\n```php\n// Change polling interval\n->databaseNotificationsPolling('60s')\n\n// Disable polling\n->databaseNotificationsPolling(null)\n```\n\n## Custom Notification Views\n\n### Blade Component\n\nCreate `resources/views/vendor/filament/components/notification.blade.php`:\n\n```blade\n\u003cx-filament::notification\n :notification=\"$notification\"\n class=\"custom-notification\"\n>\n {{ $slot }}\n\u003c/x-filament::notification>\n```\n\n### Custom Styling\n\n```css\n/* Add to your CSS */\n.filament-notification {\n @apply rounded-lg shadow-lg;\n}\n\n.filament-notification.success {\n @apply border-l-4 border-green-500;\n}\n```\n\n## Advanced Examples\n\n### Complex Notification\n\n```php\nNotification::make()\n ->title('Export completed')\n ->icon('heroicon-o-document-arrow-down')\n ->iconColor('success')\n ->body('Your data export is ready for download.')\n ->actions([\n Action::make('download')\n ->button()\n ->color('primary')\n ->icon('heroicon-o-arrow-down-tray')\n ->url(fn () => Storage::url('exports/data.csv'))\n ->openUrlInNewTab(),\n \n Action::make('dismiss')\n ->link()\n ->close()\n ->label('Dismiss'),\n ])\n ->persistent()\n ->send();\n```\n\n### Notification with Loading State\n\n```php\nAction::make('generateReport')\n ->action(function () {\n Notification::make()\n ->title('Generating report...')\n ->body('This may take a few minutes.')\n ->persistent()\n ->send();\n \n // Long running task\n $report = $this->generateReport();\n \n Notification::make()\n ->title('Report generated')\n ->success()\n ->actions([\n Action::make('download')\n ->url($report->downloadUrl()),\n ])\n ->send();\n });\n```\n\n### Conditional Notifications\n\n```php\npublic function save()\n{\n $result = $this->form->save();\n \n if ($result->successful) {\n Notification::make()\n ->title('Saved successfully')\n ->success()\n ->send();\n } else {\n Notification::make()\n ->title('Save failed')\n ->body($result->errorMessage)\n ->danger()\n ->persistent()\n ->actions([\n Action::make('retry')\n ->button()\n ->action(fn () => $this->save()),\n ])\n ->send();\n }\n}\n```\n\n## Testing Notifications\n\n```php\nuse function Pest\\Livewire\\livewire;\n\nit('shows success notification after create', function () {\n livewire(CreatePost::class)\n ->fillForm(['title' => 'Test'])\n ->call('create')\n ->assertNotified('Post created successfully');\n});\n\nit('shows custom notification', function () {\n livewire(CreatePost::class)\n ->callAction('publish')\n ->assertNotified(\n Notification::make()\n ->title('Published!')\n ->success()\n );\n});\n```\n\n## Best Practices\n\n1. **Keep messages concise** - Title should be brief, use body for details\n2. **Use appropriate types** - Success for completions, danger for errors\n3. **Add actions when helpful** - Link to affected resources\n4. **Use persistent for important** - Don't auto-dismiss critical messages\n5. **Send database notifications** - For actions requiring later attention\n6. **Test notification flow** - Verify users receive proper feedback\n7. **Don't over-notify** - Too many notifications reduce effectiveness\n8. **Use icons appropriately** - Enhance recognition with relevant icons\n9. **Consider mobile** - Ensure notifications work well on small screens\n10. **Localize messages** - Use translation keys for multi-language support\n\n## Additional Resources\n\n- [Official Notifications Documentation](https://filamentphp.com/docs/5.x/notifications/overview)\n- [Database Notifications](https://filamentphp.com/docs/5.x/notifications/database-notifications)\n- [Broadcast Notifications](https://filamentphp.com/docs/5.x/notifications/broadcast-notifications)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10053,"content_sha256":"9d1f570b780f52f82a178d2058eac7f982496d67d747fa2bcf26b9341c765dfc"},{"filename":"references/resources.md","content":"# Resources Reference\n\nComplete guide for creating and configuring Filament v5 resources (CRUD interfaces).\n\n## Creating Resources\n\n```bash\n# Basic resource with all pages\nphp artisan make:filament-resource Post\n\n# With automatic model and migration generation\nphp artisan make:filament-resource Post --generate\n\n# Simple resource (modals only, no dedicated pages)\nphp artisan make:filament-resource Post --simple\n\n# With view page\nphp artisan make:filament-resource Post --view\n\n# With soft delete support\nphp artisan make:filament-resource Post --soft-deletes\n\n# Simple with soft deletes\nphp artisan make:filament-resource Post --simple --soft-deletes\n```\n\n## Resource Structure\n\nA complete resource includes:\n\n```\napp/Filament/Resources/\n└── PostResource/\n ├── PostResource.php # Main resource class\n ├── Pages/\n │ ├── CreatePost.php # Create page\n │ ├── EditPost.php # Edit page\n │ ├── ListPosts.php # List page (table)\n │ └── ViewPost.php # View page (optional)\n ├── RelationManagers/\n │ └── CommentsRelationManager.php\n └── Widgets/\n └── PostStatsWidget.php\n```\n\n## Basic Resource Class\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources;\n\nuse App\\Filament\\Resources\\PostResource\\Pages;\nuse App\\Filament\\Resources\\PostResource\\RelationManagers;\nuse App\\Models\\Post;\nuse Filament\\Forms;\nuse Filament\\Forms\\Form;\nuse Filament\\Resources\\Resource;\nuse Filament\\Tables;\nuse Filament\\Tables\\Table;\nuse Illuminate\\Database\\Eloquent\\Builder;\n\nclass PostResource extends Resource\n{\n // Model configuration\n protected static ?string $model = Post::class;\n \n // UI configuration\n protected static ?string $navigationIcon = 'heroicon-o-document-text';\n protected static ?string $navigationGroup = 'Content';\n protected static ?int $navigationSort = 1;\n protected static ?string $recordTitleAttribute = 'title';\n protected static ?string $modelLabel = 'Blog Post';\n protected static ?string $pluralModelLabel = 'Blog Posts';\n \n // Authorization\n protected static bool $shouldSkipAuthorization = false;\n \n public static function form(Form $form): Form\n {\n return $form\n ->schema([\n // Form fields\n ]);\n }\n \n public static function table(Table $table): Table\n {\n return $table\n ->columns([\n // Table columns\n ])\n ->filters([\n // Filters\n ])\n ->actions([\n // Row actions\n ])\n ->bulkActions([\n // Bulk actions\n ]);\n }\n \n public static function getRelations(): array\n {\n return [\n RelationManagers\\CommentsRelationManager::class,\n ];\n }\n \n public static function getPages(): array\n {\n return [\n 'index' => Pages\\ListPosts::class,\n 'create' => Pages\\CreatePost::class,\n 'view' => Pages\\ViewPost::class,\n 'edit' => Pages\\EditPost::class,\n ];\n }\n \n public static function getEloquentQuery(): Builder\n {\n return parent::getEloquentQuery()\n ->with(['author', 'category'])\n ->withoutGlobalScopes([\n // SoftDeletingScope::class,\n ]);\n }\n}\n```\n\n## Resource Configuration Options\n\n### Navigation\n\n```php\nprotected static ?string $navigationIcon = 'heroicon-o-document-text';\nprotected static ?string $navigationGroup = 'Content';\nprotected static ?int $navigationSort = 1;\nprotected static ?string $navigationLabel = 'All Posts';\nprotected static ?string $activeNavigationIcon = 'heroicon-s-document-text';\n\n// Hide from navigation\nprotected static bool $shouldRegisterNavigation = false;\n\n// Dynamic navigation visibility\npublic static function shouldRegisterNavigation(): bool\n{\n return auth()->user()->can('view posts');\n}\n```\n\n### Labels\n\n```php\nprotected static ?string $modelLabel = 'Blog Post';\nprotected static ?string $pluralModelLabel = 'Blog Posts';\nprotected static ?string $recordTitleAttribute = 'title';\n\n// Dynamic titles\npublic static function getGlobalSearchResultTitle(Model $record): string\n{\n return $record->title;\n}\n\npublic static function getNavigationLabel(): string\n{\n return 'All Posts';\n}\n```\n\n### Slugs and Routes\n\n```php\nprotected static ?string $slug = 'posts';\n\n// Dynamic slug\npublic static function getSlug(): string\n{\n return 'blog-posts';\n}\n```\n\n## Form Configuration\n\n```php\npublic static function form(Form $form): Form\n{\n return $form\n ->schema([\n Forms\\Components\\Section::make('Content')\n ->schema([\n Forms\\Components\\TextInput::make('title')\n ->required()\n ->maxLength(255)\n ->live(onBlur: true)\n ->afterStateUpdated(fn ($state, $set) => \n $set('slug', \\Illuminate\\Support\\Str::slug($state))\n ),\n \n Forms\\Components\\TextInput::make('slug')\n ->required()\n ->unique(ignoreRecord: true),\n \n Forms\\Components\\RichEditor::make('content')\n ->required()\n ->columnSpanFull(),\n ]),\n \n Forms\\Components\\Section::make('Publishing')\n ->schema([\n Forms\\Components\\Select::make('status')\n ->options([\n 'draft' => 'Draft',\n 'published' => 'Published',\n 'archived' => 'Archived',\n ])\n ->required(),\n \n Forms\\Components\\DateTimePicker::make('published_at'),\n \n Forms\\Components\\Toggle::make('is_featured'),\n ])\n ->columns(2),\n \n Forms\\Components\\Section::make('Relationships')\n ->schema([\n Forms\\Components\\Select::make('author_id')\n ->relationship('author', 'name')\n ->searchable()\n ->preload()\n ->required(),\n \n Forms\\Components\\Select::make('category_id')\n ->relationship('category', 'name')\n ->searchable()\n ->preload(),\n \n Forms\\Components\\Select::make('tags')\n ->relationship('tags', 'name')\n ->multiple()\n ->preload()\n ->searchable(),\n ]),\n ])\n ->columns(1);\n}\n```\n\n## Table Configuration\n\n```php\npublic static function table(Table $table): Table\n{\n return $table\n ->columns([\n Tables\\Columns\\TextColumn::make('title')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('status')\n ->badge()\n ->color(fn (string $state): string => match ($state) {\n 'draft' => 'gray',\n 'published' => 'success',\n 'archived' => 'danger',\n }),\n \n Tables\\Columns\\TextColumn::make('author.name')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('created_at')\n ->dateTime()\n ->sortable(),\n ])\n ->filters([\n Tables\\Filters\\SelectFilter::make('status')\n ->options([\n 'draft' => 'Draft',\n 'published' => 'Published',\n 'archived' => 'Archived',\n ]),\n ])\n ->actions([\n Tables\\Actions\\ViewAction::make(),\n Tables\\Actions\\EditAction::make(),\n Tables\\Actions\\DeleteAction::make(),\n ])\n ->bulkActions([\n Tables\\Actions\\BulkActionGroup::make([\n Tables\\Actions\\DeleteBulkAction::make(),\n ]),\n ])\n ->defaultSort('created_at', 'desc');\n}\n```\n\n## Resource Pages\n\n### List Page\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources\\PostResource\\Pages;\n\nuse App\\Filament\\Resources\\PostResource;\nuse Filament\\Actions;\nuse Filament\\Resources\\Pages\\ListRecords;\n\nclass ListPosts extends ListRecords\n{\n protected static string $resource = PostResource::class;\n \n protected function getHeaderActions(): array\n {\n return [\n Actions\\CreateAction::make(),\n ];\n }\n \n // Custom title\n protected function getHeader(): ?array\n {\n return [\n 'heading' => 'Blog Posts',\n 'subheading' => 'Manage your blog content',\n ];\n }\n}\n```\n\n### Create Page\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources\\PostResource\\Pages;\n\nuse App\\Filament\\Resources\\PostResource;\nuse Filament\\Resources\\Pages\\CreateRecord;\n\nclass CreatePost extends CreateRecord\n{\n protected static string $resource = PostResource::class;\n \n // Custom redirect after creation\n protected function getRedirectUrl(): string\n {\n return $this->getResource()::getUrl('index');\n }\n \n // Mutate form data before creation\n protected function mutateFormDataBeforeCreate(array $data): array\n {\n $data['user_id'] = auth()->id();\n return $data;\n }\n}\n```\n\n### Edit Page\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources\\PostResource\\Pages;\n\nuse App\\Filament\\Resources\\PostResource;\nuse Filament\\Actions;\nuse Filament\\Resources\\Pages\\EditRecord;\n\nclass EditPost extends EditRecord\n{\n protected static string $resource = PostResource::class;\n \n protected function getHeaderActions(): array\n {\n return [\n Actions\\ViewAction::make(),\n Actions\\DeleteAction::make(),\n ];\n }\n \n protected function getRedirectUrl(): string\n {\n return $this->getResource()::getUrl('index');\n }\n \n protected function mutateFormDataBeforeSave(array $data): array\n {\n $data['updated_by'] = auth()->id();\n return $data;\n }\n}\n```\n\n### View Page\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources\\PostResource\\Pages;\n\nuse App\\Filament\\Resources\\PostResource;\nuse Filament\\Actions;\nuse Filament\\Resources\\Pages\\ViewRecord;\n\nclass ViewPost extends ViewRecord\n{\n protected static string $resource = PostResource::class;\n \n protected function getHeaderActions(): array\n {\n return [\n Actions\\EditAction::make(),\n Actions\\DeleteAction::make(),\n ];\n }\n}\n```\n\n## Relation Managers\n\n### Creating Relation Managers\n\n```bash\nphp artisan make:filament-relation-manager PostResource comments id\n```\n\n### Relation Manager Class\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources\\PostResource\\RelationManagers;\n\nuse Filament\\Forms;\nuse Filament\\Forms\\Form;\nuse Filament\\Resources\\RelationManagers\\RelationManager;\nuse Filament\\Tables;\nuse Filament\\Tables\\Table;\n\nclass CommentsRelationManager extends RelationManager\n{\n protected static string $relationship = 'comments';\n \n protected static ?string $title = 'Comments';\n \n protected static ?string $recordTitleAttribute = 'content';\n \n public function form(Form $form): Form\n {\n return $form\n ->schema([\n Forms\\Components\\Textarea::make('content')\n ->required()\n ->maxLength(1000)\n ->columnSpanFull(),\n \n Forms\\Components\\Toggle::make('is_approved')\n ->default(true),\n ]);\n }\n \n public function table(Table $table): Table\n {\n return $table\n ->recordTitleAttribute('content')\n ->columns([\n Tables\\Columns\\TextColumn::make('content')\n ->limit(50),\n \n Tables\\Columns\\TextColumn::make('user.name'),\n \n Tables\\Columns\\IconColumn::make('is_approved')\n ->boolean(),\n \n Tables\\Columns\\TextColumn::make('created_at')\n ->dateTime(),\n ])\n ->filters([\n Tables\\Filters\\TernaryFilter::make('is_approved'),\n ])\n ->headerActions([\n Tables\\Actions\\CreateAction::make(),\n ])\n ->actions([\n Tables\\Actions\\EditAction::make(),\n Tables\\Actions\\DeleteAction::make(),\n ])\n ->bulkActions([\n Tables\\Actions\\BulkActionGroup::make([\n Tables\\Actions\\DeleteBulkAction::make(),\n ]),\n ]);\n }\n}\n```\n\n### Registering Relations\n\n```php\npublic static function getRelations(): array\n{\n return [\n RelationManagers\\CommentsRelationManager::class,\n RelationManagers\\TagsRelationManager::class,\n ];\n}\n```\n\n## Global Search\n\nEnable global search for a resource:\n\n```php\nprotected static ?string $recordTitleAttribute = 'title';\n\npublic static function getGlobalSearchResultTitle(Model $record): string\n{\n return $record->title;\n}\n\npublic static function getGlobalSearchResultDetails(Model $record): array\n{\n return [\n 'Author' => $record->author->name,\n 'Status' => $record->status,\n ];\n}\n\npublic static function getGlobalSearchEloquentQuery(): Builder\n{\n return parent::getGlobalSearchEloquentQuery()\n ->with(['author']);\n}\n\npublic static function getGloballySearchableAttributes(): array\n{\n return ['title', 'content', 'slug'];\n}\n```\n\n## Sub-Navigation\n\nAdd tabs to the resource:\n\n```php\npublic static function getRecordSubNavigation(Page $page): array\n{\n return $page->generateNavigationItems([\n Pages\\ViewPost::class,\n Pages\\EditPost::class,\n Pages\\ManageComments::class,\n ]);\n}\n```\n\n## Custom Pages\n\nCreate custom resource pages:\n\n```bash\nphp artisan make:filament-page ManagePostComments \\\n --resource=PostResource \\\n --type=ManageRelatedRecords\n```\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources\\PostResource\\Pages;\n\nuse App\\Filament\\Resources\\PostResource;\nuse Filament\\Resources\\Pages\\ManageRelatedRecords;\nuse Filament\\Tables;\n\nclass ManageComments extends ManageRelatedRecords\n{\n protected static string $resource = PostResource::class;\n \n protected static string $relationship = 'comments';\n \n protected static ?string $navigationIcon = 'heroicon-o-chat-bubble-left-ellipsis';\n \n public function table(Tables\\Table $table): Tables\\Table\n {\n return $table\n ->recordTitleAttribute('content')\n ->columns([\n Tables\\Columns\\TextColumn::make('content'),\n Tables\\Columns\\TextColumn::make('user.name'),\n ]);\n }\n}\n```\n\n## Soft Deletes\n\nEnable soft delete support:\n\n```bash\nphp artisan make:filament-resource Post --soft-deletes\n```\n\n```php\npublic static function getEloquentQuery(): Builder\n{\n return parent::getEloquentQuery()\n ->withoutGlobalScopes([\n SoftDeletingScope::class,\n ]);\n}\n\n// Add actions in table\n->actions([\n Tables\\Actions\\EditAction::make(),\n Tables\\Actions\\DeleteAction::make(),\n Tables\\Actions\\ForceDeleteAction::make(),\n Tables\\Actions\\RestoreAction::make(),\n])\n\n// Add filter\n->filters([\n Tables\\Filters\\TrashedFilter::make(),\n])\n```\n\n## Authorization\n\n```php\npublic static function canViewAny(): bool\n{\n return auth()->user()->can('view posts');\n}\n\npublic static function canCreate(): bool\n{\n return auth()->user()->can('create posts');\n}\n\npublic static function canEdit(Model $record): bool\n{\n return auth()->user()->can('update', $record);\n}\n\npublic static function canDelete(Model $record): bool\n{\n return auth()->user()->can('delete', $record);\n}\n\npublic static function canView(Model $record): bool\n{\n return auth()->user()->can('view', $record);\n}\n```\n\n## Infolist (View Page Details)\n\n```php\nuse Filament\\Infolists\\Components\\TextEntry;\nuse Filament\\Infolists\\Components\\ImageEntry;\nuse Filament\\Infolists\\Infolist;\n\npublic static function infolist(Infolist $infolist): Infolist\n{\n return $infolist\n ->schema([\n TextEntry::make('title'),\n TextEntry::make('content')\n ->html()\n ->columnSpanFull(),\n TextEntry::make('author.name'),\n ImageEntry::make('featured_image'),\n ]);\n}\n```\n\n## Complete Resource Example\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Resources;\n\nuse App\\Filament\\Resources\\PostResource\\Pages;\nuse App\\Filament\\Resources\\PostResource\\RelationManagers;\nuse App\\Models\\Post;\nuse Filament\\Forms;\nuse Filament\\Forms\\Form;\nuse Filament\\Infolists\\Components\\ImageEntry;\nuse Filament\\Infolists\\Components\\TextEntry;\nuse Filament\\Infolists\\Infolist;\nuse Filament\\Resources\\Resource;\nuse Filament\\Tables;\nuse Filament\\Tables\\Table;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Database\\Eloquent\\SoftDeletingScope;\nuse Illuminate\\Support\\Str;\n\nclass PostResource extends Resource\n{\n protected static ?string $model = Post::class;\n protected static ?string $navigationIcon = 'heroicon-o-document-text';\n protected static ?string $navigationGroup = 'Content';\n protected static ?int $navigationSort = 1;\n protected static ?string $recordTitleAttribute = 'title';\n \n public static function form(Form $form): Form\n {\n return $form\n ->schema([\n Forms\\Components\\Section::make('Basic Information')\n ->schema([\n Forms\\Components\\TextInput::make('title')\n ->required()\n ->maxLength(255)\n ->live(onBlur: true)\n ->afterStateUpdated(fn ($state, $set) => \n $set('slug', Str::slug($state))\n ),\n \n Forms\\Components\\TextInput::make('slug')\n ->required()\n ->unique(ignoreRecord: true),\n \n Forms\\Components\\RichEditor::make('content')\n ->required()\n ->columnSpanFull(),\n ]),\n \n Forms\\Components\\Section::make('Publishing')\n ->schema([\n Forms\\Components\\Select::make('status')\n ->options([\n 'draft' => 'Draft',\n 'published' => 'Published',\n 'archived' => 'Archived',\n ])\n ->required(),\n \n Forms\\Components\\DateTimePicker::make('published_at'),\n \n Forms\\Components\\Toggle::make('is_featured'),\n ])\n ->columns(2),\n \n Forms\\Components\\Section::make('Relationships')\n ->schema([\n Forms\\Components\\Select::make('author_id')\n ->relationship('author', 'name')\n ->searchable()\n ->preload()\n ->required(),\n \n Forms\\Components\\Select::make('category_id')\n ->relationship('category', 'name')\n ->searchable()\n ->preload(),\n ])\n ->columns(2),\n ])\n ->columns(1);\n }\n \n public static function table(Table $table): Table\n {\n return $table\n ->columns([\n Tables\\Columns\\TextColumn::make('title')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('status')\n ->badge()\n ->color(fn (string $state): string => match ($state) {\n 'draft' => 'gray',\n 'published' => 'success',\n 'archived' => 'danger',\n }),\n \n Tables\\Columns\\TextColumn::make('author.name')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('published_at')\n ->dateTime()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('created_at')\n ->dateTime()\n ->sortable()\n ->toggleable(isToggledHiddenByDefault: true),\n ])\n ->filters([\n Tables\\Filters\\SelectFilter::make('status')\n ->options([\n 'draft' => 'Draft',\n 'published' => 'Published',\n 'archived' => 'Archived',\n ]),\n \n Tables\\Filters\\TrashedFilter::make(),\n ])\n ->actions([\n Tables\\Actions\\ViewAction::make(),\n Tables\\Actions\\EditAction::make(),\n Tables\\Actions\\DeleteAction::make(),\n Tables\\Actions\\ForceDeleteAction::make(),\n Tables\\Actions\\RestoreAction::make(),\n ])\n ->bulkActions([\n Tables\\Actions\\BulkActionGroup::make([\n Tables\\Actions\\DeleteBulkAction::make(),\n Tables\\Actions\\ForceDeleteBulkAction::make(),\n Tables\\Actions\\RestoreBulkAction::make(),\n ]),\n ])\n ->defaultSort('created_at', 'desc');\n }\n \n public static function infolist(Infolist $infolist): Infolist\n {\n return $infolist\n ->schema([\n TextEntry::make('title'),\n TextEntry::make('slug'),\n TextEntry::make('content')\n ->html()\n ->columnSpanFull(),\n TextEntry::make('author.name'),\n TextEntry::make('status'),\n TextEntry::make('published_at'),\n ImageEntry::make('featured_image'),\n ]);\n }\n \n public static function getRelations(): array\n {\n return [\n RelationManagers\\CommentsRelationManager::class,\n ];\n }\n \n public static function getPages(): array\n {\n return [\n 'index' => Pages\\ListPosts::class,\n 'create' => Pages\\CreatePost::class,\n 'view' => Pages\\ViewPost::class,\n 'edit' => Pages\\EditPost::class,\n ];\n }\n \n public static function getEloquentQuery(): Builder\n {\n return parent::getEloquentQuery()\n ->withoutGlobalScopes([\n SoftDeletingScope::class,\n ]);\n }\n}\n```\n\n## Best Practices\n\n1. **Use eager loading** in `getEloquentQuery()` to prevent N+1\n2. **Organize forms with Sections** for better UX\n3. **Add search and sort** to frequently accessed columns\n4. **Use colors on status badges** for visual feedback\n5. **Implement authorization** with policies\n6. **Add relation managers** for connected data\n7. **Enable soft deletes** for data safety\n8. **Use toggleable columns** for wide tables\n9. **Add global search** for better discoverability\n10. **Keep forms focused** - use multiple sections or tabs\n11. **Validate all input** with appropriate rules\n12. **Test with large datasets** to ensure performance\n13. **Use infolists** on view pages for read-only data\n14. **Add helpful actions** like duplicate or export\n15. **Document your resources** with comments\n\n## Additional Resources\n\n- [Official Resources Documentation](https://filamentphp.com/docs/5.x/resources/overview)\n- [Relation Managers](https://filamentphp.com/docs/5.x/resources/relation-managers)\n- [Global Search](https://filamentphp.com/docs/5.x/resources/global-search)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":24527,"content_sha256":"a2a9dae7c5cfc14f3f872f12c233ad1231e4ca0e01d07d61407b556803bfbf03"},{"filename":"references/schemas.md","content":"# Schemas Reference\n\nComplete guide for understanding and using Filament v5's schema system.\n\n## Overview\n\nSchemas are the foundation of Filament's Server-Driven UI approach. They allow you to build user interfaces declaratively using PHP configuration objects rather than writing HTML or JavaScript. Schemas define the structure and behavior of forms, infolists, tables, and layouts.\n\n## What Are Schemas?\n\nA schema is a collection of components that define:\n- **Form fields** and their validation rules\n- **Infolist entries** for displaying data\n- **Table columns** and their formatting\n- **Layout containers** (grids, sections, tabs)\n- **Action definitions** and their behavior\n\n## Schema Types\n\n### Form Schemas\n\nUsed in resources, custom pages, and actions:\n\n```php\nuse Filament\\Forms\\Components\\TextInput;\nuse Filament\\Forms\\Components\\Select;\nuse Filament\\Forms\\Components\\Section;\nuse Filament\\Forms\\Form;\n\npublic function form(Form $form): Form\n{\n return $form\n ->schema([\n Section::make('Details')\n ->schema([\n TextInput::make('name')\n ->required()\n ->maxLength(255),\n \n Select::make('status')\n ->options(['draft' => 'Draft', 'published' => 'Published'])\n ->required(),\n ])\n ->columns(2),\n ]);\n}\n```\n\n### Infolist Schemas\n\nUsed for read-only data display:\n\n```php\nuse Filament\\Infolists\\Components\\TextEntry;\nuse Filament\\Infolists\\Components\\Section;\nuse Filament\\Infolists\\Infolist;\n\npublic function infolist(Infolist $infolist): Infolist\n{\n return $infolist\n ->schema([\n Section::make('Details')\n ->schema([\n TextEntry::make('name'),\n TextEntry::make('status')\n ->badge(),\n ])\n ->columns(2),\n ]);\n}\n```\n\n### Table Schemas\n\nDefine table columns and filters:\n\n```php\nuse Filament\\Tables\\Columns\\TextColumn;\nuse Filament\\Tables\\Columns\\BadgeColumn;\nuse Filament\\Tables\\Table;\n\npublic function table(Table $table): Table\n{\n return $table\n ->columns([\n TextColumn::make('name')\n ->searchable(),\n BadgeColumn::make('status')\n ->color(fn ($state) => match ($state) {\n 'draft' => 'gray',\n 'published' => 'success',\n }),\n ])\n ->filters([\n // Filters\n ]);\n}\n```\n\n## Layout Components\n\n### Grid\n\nOrganize components in columns:\n\n```php\nuse Filament\\Schemas\\Components\\Grid;\n\nGrid::make(2)\n ->schema([\n TextInput::make('first_name'),\n TextInput::make('last_name'),\n ])\n\n// Responsive grid\nGrid::make([\n 'default' => 1,\n 'sm' => 2,\n 'md' => 3,\n 'lg' => 4,\n])\n ->schema([\n // Components\n ])\n```\n\n### Section\n\nGroup components with a heading:\n\n```php\nuse Filament\\Schemas\\Components\\Section;\n\nSection::make('Personal Information')\n ->description('Enter your personal details')\n ->icon('heroicon-m-user')\n ->collapsible()\n ->collapsed()\n ->compact()\n ->aside() // Side-by-side layout\n ->schema([\n TextInput::make('name'),\n TextInput::make('email'),\n ])\n ->columns(2);\n```\n\n### Tabs\n\nOrganize into tabs:\n\n```php\nuse Filament\\Schemas\\Components\\Tabs;\n\nTabs::make('Settings')\n ->tabs([\n Tabs\\Tab::make('General')\n ->icon('heroicon-m-cog')\n ->schema([\n TextInput::make('site_name'),\n ]),\n \n Tabs\\Tab::make('SEO')\n ->icon('heroicon-m-globe')\n ->schema([\n TextInput::make('meta_title'),\n ]),\n ]);\n```\n\n### Wizard\n\nMulti-step form:\n\n```php\nuse Filament\\Schemas\\Components\\Wizard;\n\nWizard::make([\n Wizard\\Step::make('Account')\n ->icon('heroicon-m-user')\n ->description('Create your account')\n ->schema([\n TextInput::make('email'),\n TextInput::make('password'),\n ]),\n \n Wizard\\Step::make('Profile')\n ->icon('heroicon-m-identification')\n ->description('Set up your profile')\n ->schema([\n TextInput::make('name'),\n ]),\n])\n ->skippable()\n ->persistInQueryString();\n```\n\n### Fieldset\n\nGroup without card styling:\n\n```php\nuse Filament\\Schemas\\Components\\Fieldset;\n\nFieldset::make('Address')\n ->schema([\n TextInput::make('street'),\n TextInput::make('city'),\n ]);\n```\n\n### Split\n\nSide-by-side layout:\n\n```php\nuse Filament\\Schemas\\Components\\Split;\n\nSplit::make([\n Section::make('Details')\n ->schema([\n TextInput::make('name'),\n ]),\n \n Section::make('Avatar')\n ->schema([\n ImageUpload::make('avatar'),\n ]),\n])\n ->from('lg'); // Breakpoint\n```\n\n### Group\n\nSimple grouping:\n\n```php\nuse Filament\\Schemas\\Components\\Group;\n\nGroup::make()\n ->schema([\n // Components\n ])\n ->columnSpanFull()\n ->columns(2);\n```\n\n## Component States\n\n### State Management\n\n```php\nuse Filament\\Schemas\\Components\\Utilities\\Get;\nuse Filament\\Schemas\\Components\\Utilities\\Set;\n\nTextInput::make('title')\n ->live(onBlur: true)\n ->afterStateUpdated(fn ($state, Set $set) => \n $set('slug', Str::slug($state))\n );\n\n// Accessing other field values\nTextInput::make('total')\n ->formatStateUsing(fn ($state, Get $get) => \n $get('quantity') * $get('price')\n )\n ->dehydrated(false);\n```\n\n### State Hydration\n\n```php\nTextInput::make('name')\n ->formatStateUsing(fn ($state) => strtoupper($state))\n ->dehydrateStateUsing(fn ($state) => strtolower($state));\n```\n\n### Default Values\n\n```php\nTextInput::make('status')\n ->default('draft');\n\nSelect::make('role')\n ->default('user');\n\nToggle::make('is_active')\n ->default(true);\n```\n\n## Conditional Logic\n\n### Visible/Hidden\n\n```php\nTextInput::make('company_name')\n ->visible(fn (Get $get) => $get('is_company'))\n ->hidden(fn (Get $get) => !$get('is_company'));\n```\n\n### Disabled/Readonly\n\n```php\nTextInput::make('email')\n ->disabled(fn () => auth()->user()->cannot('edit_email'));\n\nTextInput::make('created_at')\n ->readonly();\n```\n\n### Required\n\n```php\nTextInput::make('company_name')\n ->required(fn (Get $get) => $get('is_company'));\n```\n\n## Schema Validation\n\n### Field-Level Validation\n\n```php\nTextInput::make('email')\n ->email()\n ->required()\n ->unique('users', 'email')\n ->maxLength(255);\n\nTextInput::make('password')\n ->password()\n ->required()\n ->minLength(8)\n ->confirmed();\n```\n\n### Custom Rules\n\n```php\nTextInput::make('username')\n ->rules(['required', 'string', 'regex:/^[a-z0-9_]+$/']);\n\nTextInput::make('code')\n ->rule(function ($state) {\n return $state === 'VALID' ? null : 'Invalid code';\n });\n```\n\n### Validation Messages\n\n```php\nTextInput::make('email')\n ->email()\n ->validationMessages([\n 'email' => 'Please enter a valid email address.',\n 'required' => 'Email is required.',\n ]);\n```\n\n## Schema Customization\n\n### Columns\n\n```php\nSection::make('Details')\n ->schema([\n TextInput::make('name'),\n TextInput::make('email'),\n TextInput::make('phone'),\n ])\n ->columns(2);\n\n// Responsive columns\nSection::make('Details')\n ->columns([\n 'default' => 1,\n 'sm' => 2,\n 'lg' => 3,\n ]);\n```\n\n### Column Span\n\n```php\nTextInput::make('title')\n ->columnSpan(2);\n\nTextInput::make('content')\n ->columnSpanFull();\n\n// Responsive span\nTextInput::make('name')\n ->columnSpan([\n 'default' => 1,\n 'lg' => 2,\n ]);\n```\n\n### Extra Attributes\n\n```php\nTextInput::make('name')\n ->extraAttributes(['class' => 'custom-class'])\n ->extraInputAttributes(['autocomplete' => 'off']);\n```\n\n## Advanced Patterns\n\n### Dynamic Schema\n\n```php\npublic function form(Form $form): Form\n{\n return $form\n ->schema(fn (): array => [\n TextInput::make('name'),\n \n // Conditionally include fields\n ...(auth()->user()->isAdmin() ? [\n TextInput::make('admin_notes'),\n ] : []),\n ]);\n}\n```\n\n### Builder Pattern\n\n```php\nuse Filament\\Forms\\Components\\Builder;\n\nBuilder::make('content')\n ->blocks([\n Builder\\Block::make('heading')\n ->schema([\n TextInput::make('content'),\n Select::make('level'),\n ]),\n \n Builder\\Block::make('paragraph')\n ->schema([\n RichEditor::make('content'),\n ]),\n ]);\n```\n\n### Repeater Pattern\n\n```php\nuse Filament\\Forms\\Components\\Repeater;\n\nRepeater::make('items')\n ->schema([\n TextInput::make('name'),\n TextInput::make('quantity'),\n ])\n ->collapsible()\n ->itemLabel(fn (array $state): ?string => $state['name'] ?? null);\n```\n\n### Schema Composition\n\n```php\n// Reusable schema class\nclass UserSchema\n{\n public static function make(): array\n {\n return [\n TextInput::make('name')->required(),\n TextInput::make('email')->email()->required(),\n ];\n }\n}\n\n// Use in resource\npublic function form(Form $form): Form\n{\n return $form\n ->schema([\n ...UserSchema::make(),\n \n TextInput::make('phone'),\n ]);\n}\n```\n\n## Schema in Different Contexts\n\n### In Resources\n\n```php\nclass PostResource extends Resource\n{\n public static function form(Form $form): Form\n {\n return $form\n ->schema([\n // Form schema\n ]);\n }\n \n public static function infolist(Infolist $infolist): Infolist\n {\n return $infolist\n ->schema([\n // Infolist schema\n ]);\n }\n \n public static function table(Table $table): Table\n {\n return $table\n ->columns([\n // Table schema (columns)\n ]);\n }\n}\n```\n\n### In Custom Pages\n\n```php\nclass Settings extends Page\n{\n protected ?array $data = [];\n \n public function form(Form $form): Form\n {\n return $form\n ->statePath('data')\n ->schema([\n TextInput::make('site_name'),\n ]);\n }\n}\n```\n\n### In Actions\n\n```php\nAction::make('edit')\n ->form([\n TextInput::make('name'),\n TextInput::make('email'),\n ])\n ->action(function (array $data) {\n // Handle data\n });\n```\n\n## Schema State Path\n\n### State Path Configuration\n\n```php\npublic function form(Form $form): Form\n{\n return $form\n ->statePath('data') // Root state path\n ->schema([\n TextInput::make('name'), // Accesses $data['name']\n \n Section::make('Address')\n ->statePath('address') // Nested path\n ->schema([\n TextInput::make('street'), // $data['address']['street']\n ]),\n ]);\n}\n```\n\n### Dehydration Control\n\n```php\nTextInput::make('display_value')\n ->dehydrated(false) // Don't save to database\n ->formatStateUsing(fn ($state, Get $get) => \n $get('quantity') * $get('price')\n );\n```\n\n## Schema Testing\n\n### Testing Forms\n\n```php\nit('validates form schema', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => null,\n ])\n ->call('create')\n ->assertHasFormErrors(['title' => 'required']);\n});\n\nit('fills form schema correctly', function () {\n livewire(EditPost::class, ['record' => $post])\n ->assertFormSet([\n 'title' => $post->title,\n ]);\n});\n```\n\n### Testing Conditional Logic\n\n```php\nit('shows company fields when is_company is true', function () {\n livewire(CreateUser::class)\n ->fillForm(['is_company' => true])\n ->assertFormFieldVisible('company_name');\n});\n```\n\n## Best Practices\n\n1. **Keep schemas organized** - Use sections and clear naming\n2. **Extract reusable schemas** - Create schema classes for common patterns\n3. **Use live sparingly** - Too many live fields hurt performance\n4. **Leverage conditional logic** - Show/hide based on context\n5. **Validate at field level** - Use built-in validation methods\n6. **Test schema behavior** - Verify conditional logic works correctly\n7. **Use responsive layouts** - Grid columns that adapt to screen size\n8. **Minimize nesting** - Avoid deeply nested structures\n9. **Document complex schemas** - Add comments for clarity\n10. **Reuse components** - Create custom components for repetition\n\n## Common Patterns\n\n### Address Schema\n\n```php\npublic static function addressSchema(): array\n{\n return [\n Grid::make(2)\n ->schema([\n TextInput::make('street')\n ->required()\n ->columnSpanFull(),\n \n TextInput::make('city')\n ->required(),\n \n TextInput::make('state')\n ->required(),\n \n TextInput::make('zip')\n ->required()\n ->maxLength(10),\n \n TextInput::make('country')\n ->required(),\n ]),\n ];\n}\n```\n\n### Contact Schema\n\n```php\npublic static function contactSchema(): array\n{\n return [\n TextInput::make('email')\n ->email()\n ->required(),\n \n TextInput::make('phone')\n ->tel(),\n \n TextInput::make('website')\n ->url()\n ->prefix('https://'),\n ];\n}\n```\n\n## Additional Resources\n\n- [Official Schemas Documentation](https://filamentphp.com/docs/5.x/schemas)\n- [Form Components](https://filamentphp.com/docs/5.x/forms)\n- [Infolist Components](https://filamentphp.com/docs/5.x/infolists)\n- [Layout Components](https://filamentphp.com/docs/5.x/schemas/layout)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14038,"content_sha256":"ebbe80586838e4e084839b608ef7cad9d99cd26d4e29f3b9b7d54f7be8a825f9"},{"filename":"references/tables.md","content":"# Table Components Reference\n\nComplete reference for Filament v5 table columns, filters, and actions.\n\n## Column Types\n\nFilament provides 10+ column types in the `Filament\\Tables\\Columns` namespace:\n\n| Column Type | Class | Description |\n|-------------|-------|-------------|\n| Text | `TextColumn` | Text display with formatting |\n| Icon | `IconColumn` | Icon/boolean display |\n| Image | `ImageColumn` | Image thumbnails |\n| Badge | `BadgeColumn` | Colored badge |\n| Color | `ColorColumn` | Color swatch |\n| Select | `SelectColumn` | Editable dropdown |\n| Toggle | `ToggleColumn` | Editable toggle |\n| Checkbox | `CheckboxColumn` | Editable checkbox |\n| Text Input | `TextInputColumn` | Editable text field |\n| Summarizers | Various | Aggregate data (sum, avg, count) |\n\n## Text Column\n\n```php\nuse Filament\\Tables\\Columns\\TextColumn;\n\nTextColumn::make('name')\n ->label('Full Name')\n ->searchable()\n ->sortable()\n ->toggleable()\n ->toggleable(isToggledHiddenByDefault: true)\n ->weight('font-bold')\n ->color('primary')\n ->icon('heroicon-m-user')\n ->iconPosition('before')\n ->iconColor('success')\n ->url(fn ($record) => route('users.show', $record))\n ->openUrlInNewTab()\n ->copyable()\n ->copyMessage('Copied!')\n ->limit(50)\n ->tooltip(fn ($record): string => $record->description)\n ->wrap()\n ->alignLeft()\n ->alignCenter()\n ->alignRight()\n ->placeholder('N/A')\n ->prefix('

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

)\n ->suffix('.00')\n ->formatStateUsing(fn ($state) => strtoupper($state))\n```\n\n### Date & Number Formatting\n\n```php\nTextColumn::make('price')\n ->money('USD')\n ->sortable()\n\nTextColumn::make('quantity')\n ->numeric(decimalPlaces: 0)\n ->suffix(' items')\n\nTextColumn::make('percentage')\n ->numeric(decimalPlaces: 2)\n ->suffix('%')\n\nTextColumn::make('created_at')\n ->dateTime('M j, Y H:i')\n ->sortable()\n\nTextColumn::make('published_at')\n ->date('M j, Y')\n ->placeholder('Not published')\n\nTextColumn::make('updated_at')\n ->since() // \"2 hours ago\"\n\nTextColumn::make('size')\n ->bytes()\n\nTextColumn::make('download_count')\n ->numeric()\n ->summarize(Sum::make()->label('Total Downloads'))\n```\n\n## Badge Column\n\n```php\nuse Filament\\Tables\\Columns\\BadgeColumn;\n\nBadgeColumn::make('status')\n ->color(fn (string $state): string => match ($state) {\n 'draft' => 'gray',\n 'pending' => 'warning',\n 'published' => 'success',\n 'rejected' => 'danger',\n default => 'gray',\n })\n ->icon(fn (string $state): string => match ($state) {\n 'published' => 'heroicon-m-check-badge',\n 'pending' => 'heroicon-m-clock',\n default => null,\n })\n ->iconPosition('before')\n```\n\n## Icon Column\n\n```php\nuse Filament\\Tables\\Columns\\IconColumn;\n\nIconColumn::make('is_active')\n ->boolean()\n ->trueIcon('heroicon-o-check-circle')\n ->falseIcon('heroicon-o-x-circle')\n ->trueColor('success')\n ->falseColor('danger')\n\nIconColumn::make('status')\n ->icon(fn (string $state): string => match ($state) {\n 'active' => 'heroicon-m-check-circle',\n 'inactive' => 'heroicon-m-x-circle',\n default => 'heroicon-m-question-mark-circle',\n })\n ->color(fn (string $state): string => match ($state) {\n 'active' => 'success',\n 'inactive' => 'danger',\n default => 'gray',\n })\n```\n\n## Image Column\n\n```php\nuse Filament\\Tables\\Columns\\ImageColumn;\n\nImageColumn::make('avatar')\n ->disk('public')\n ->square()\n ->circular()\n ->size(50)\n ->width(100)\n ->height(100)\n ->defaultImageUrl(fn ($record) => 'https://ui-avatars.com/api/?name=' . urlencode($record->name))\n ->checkFileExistence()\n```\n\n## Color Column\n\n```php\nuse Filament\\Tables\\Columns\\ColorColumn;\n\nColorColumn::make('color')\n ->copyable()\n```\n\n## Select Column (Editable)\n\n```php\nuse Filament\\Tables\\Columns\\SelectColumn;\n\nSelectColumn::make('status')\n ->options([\n 'draft' => 'Draft',\n 'published' => 'Published',\n ])\n ->selectablePlaceholder(false)\n ->disablePlaceholderSelection()\n```\n\n## Toggle Column (Editable)\n\n```php\nuse Filament\\Tables\\Columns\\ToggleColumn;\n\nToggleColumn::make('is_featured')\n ->onColor('success')\n ->offColor('danger')\n ->onIcon('heroicon-m-check')\n ->offIcon('heroicon-m-x-mark')\n```\n\n## Checkbox Column (Editable)\n\n```php\nuse Filament\\Tables\\Columns\\CheckboxColumn;\n\nCheckboxColumn::make('is_approved')\n```\n\n## Text Input Column (Editable)\n\n```php\nuse Filament\\Tables\\Columns\\TextInputColumn;\n\nTextInputColumn::make('sort_order')\n ->type('number')\n ->rules(['required', 'integer', 'min:0'])\n```\n\n## Summarizers\n\n```php\nuse Filament\\Tables\\Columns\\Summarizers\\Sum;\nuse Filament\\Tables\\Columns\\Summarizers\\Average;\nuse Filament\\Tables\\Columns\\Summarizers\\Count;\nuse Filament\\Tables\\Columns\\Summarizers\\Range;\n\nTextColumn::make('price')\n ->money('USD')\n ->summarize([\n Sum::make()->label('Total'),\n Average::make()->label('Average'),\n ])\n\nTextColumn::make('quantity')\n ->numeric()\n ->summarize([\n Sum::make(),\n Range::make()->label('Range'),\n ])\n\nTextColumn::make('id')\n ->label('Count')\n ->summarize(Count::make())\n```\n\n## Filters\n\n### Basic Filters\n\n```php\nuse Filament\\Tables\\Filters\\Filter;\nuse Filament\\Tables\\Filters\\SelectFilter;\nuse Filament\\Tables\\Filters\\TernaryFilter;\nuse Filament\\Tables\\Filters\\QueryBuilder;\nuse Illuminate\\Database\\Eloquent\\Builder;\n\n// Toggle filter\nFilter::make('is_featured')\n ->query(fn (Builder $query) => $query->where('is_featured', true))\n ->toggle()\n ->label('Featured only')\n \n// Select filter\nSelectFilter::make('status')\n ->options([\n 'draft' => 'Draft',\n 'pending' => 'Pending',\n 'published' => 'Published',\n ])\n ->multiple()\n ->searchable()\n ->preload()\n ->native(false)\n \n// Ternary filter (yes/no/any)\nTernaryFilter::make('email_verified_at')\n ->label('Email verified')\n ->placeholder('Any')\n ->trueLabel('Verified')\n ->falseLabel('Not verified')\n ->native(false)\n \n// Relationship filter\nSelectFilter::make('category')\n ->relationship('category', 'name')\n ->searchable()\n ->preload()\n ->multiple()\n```\n\n### Filter with Form\n\n```php\nuse Filament\\Forms\\Components\\DatePicker;\n\nFilter::make('created_at')\n ->form([\n DatePicker::make('created_from'),\n DatePicker::make('created_until'),\n ])\n ->query(function (Builder $query, array $data): Builder {\n return $query\n ->when(\n $data['created_from'],\n fn (Builder $query, $date): Builder => $query->whereDate('created_at', '>=', $date),\n )\n ->when(\n $data['created_until'],\n fn (Builder $query, $date): Builder => $query->whereDate('created_at', '\u003c=', $date),\n );\n })\n```\n\n### Filter Groups\n\n```php\nuse Filament\\Tables\\Filters\\FilterGroup;\n\nFilterGroup::make('Status', [\n Filter::make('draft')\n ->query(fn ($query) => $query->where('status', 'draft')),\n Filter::make('published')\n ->query(fn ($query) => $query->where('status', 'published')),\n Filter::make('archived')\n ->query(fn ($query) => $query->where('status', 'archived')),\n])\n```\n\n## Table Actions\n\n### Record Actions\n\n```php\nuse Filament\\Actions\\Action;\nuse Filament\\Actions\\EditAction;\nuse Filament\\Actions\\DeleteAction;\nuse Filament\\Actions\\ViewAction;\nuse Filament\\Actions\\ReplicateAction;\nuse Filament\\Actions\\ForceDeleteAction;\nuse Filament\\Actions\\RestoreAction;\nuse Filament\\Notifications\\Notification;\n\n->actions([\n ViewAction::make(),\n EditAction::make(),\n DeleteAction::make(),\n \n // Custom action\n Action::make('approve')\n ->icon('heroicon-m-check-circle')\n ->color('success')\n ->requiresConfirmation()\n ->modalHeading('Approve post')\n ->modalDescription('Are you sure you want to approve this post?')\n ->modalSubmitActionLabel('Yes, approve')\n ->action(function (Post $record) {\n $record->update(['status' => 'approved']);\n \n Notification::make()\n ->title('Post approved')\n ->success()\n ->send();\n })\n ->visible(fn (Post $record): bool => $record->status === 'pending'),\n \n // Action with modal form\n Action::make('sendEmail')\n ->icon('heroicon-m-envelope')\n ->form([\n TextInput::make('subject')->required(),\n RichEditor::make('body')->required(),\n ])\n ->action(function (array $data, Post $record) {\n Mail::to($record->author->email)\n ->send(new PostNotification($data['subject'], $data['body']));\n })\n ->successNotificationTitle('Email sent'),\n \n // Open URL\n Action::make('preview')\n ->icon('heroicon-m-eye')\n ->url(fn (Post $record): string => route('posts.preview', $record))\n ->openUrlInNewTab(),\n \n // Action group\n ActionGroup::make([\n Action::make('edit')\n ->icon('heroicon-m-pencil-square')\n ->url(fn (Post $record): string => route('posts.edit', $record)),\n Action::make('duplicate')\n ->icon('heroicon-m-document-duplicate')\n ->action(fn (Post $record) => $record->replicate()->save()),\n Action::make('delete')\n ->icon('heroicon-m-trash')\n ->color('danger')\n ->requiresConfirmation()\n ->action(fn (Post $record) => $record->delete()),\n ])\n ->label('Actions')\n ->icon('heroicon-m-ellipsis-vertical')\n ->size(ActionSize::Small)\n ->color('gray'),\n])\n```\n\n### Header Actions\n\n```php\n->headerActions([\n Action::make('create')\n ->label('New Post')\n ->icon('heroicon-m-plus')\n ->url(fn (): string => route('posts.create')),\n \n Action::make('import')\n ->icon('heroicon-m-arrow-up-tray')\n ->form([\n FileUpload::make('file')\n ->acceptedFileTypes(['text/csv'])\n ->required(),\n ])\n ->action(function (array $data) {\n // Import logic\n }),\n])\n```\n\n### Bulk Actions\n\n```php\nuse Filament\\Actions\\BulkActionGroup;\nuse Filament\\Actions\\DeleteBulkAction;\nuse Filament\\Tables\\Actions\\BulkAction;\n\n->bulkActions([\n BulkActionGroup::make([\n DeleteBulkAction::make(),\n \n BulkAction::make('publish')\n ->icon('heroicon-m-check-circle')\n ->color('success')\n ->requiresConfirmation()\n ->action(fn (Collection $records) => \n $records->each->update(['status' => 'published'])\n ),\n \n BulkAction::make('changeStatus')\n ->icon('heroicon-m-pencil-square')\n ->form([\n Select::make('status')\n ->options(['draft' => 'Draft', 'published' => 'Published'])\n ->required(),\n ])\n ->action(fn (Collection $records, array $data) => \n $records->each->update(['status' => $data['status']])\n ),\n \n BulkAction::make('export')\n ->icon('heroicon-m-arrow-down-tray')\n ->action(function (Collection $records) {\n // Export logic\n return response()->download($path);\n }),\n ]),\n])\n```\n\n## Table Configuration\n\n```php\npublic static function table(Table $table): Table\n{\n return $table\n ->columns([\n // ... columns\n ])\n ->filters([\n // ... filters\n ])\n ->actions([\n // ... actions\n ])\n ->bulkActions([\n // ... bulk actions\n ])\n ->defaultSort('created_at', 'desc')\n ->defaultPaginationPageOption(25)\n ->paginated([10, 25, 50, 100])\n ->searchable()\n ->searchPlaceholder('Search posts...')\n ->searchDebounce(500) // ms\n ->searchOnBlur()\n ->recordClasses(fn (Post $record) => match ($record->status) {\n 'draft' => 'bg-gray-50',\n 'published' => null,\n default => null,\n })\n ->recordUrl(fn (Post $record): string => route('posts.edit', $record))\n ->recordAction(EditAction::class)\n ->striped()\n ->poll('30s')\n ->emptyStateHeading('No posts yet')\n ->emptyStateDescription('Create a post to get started.')\n ->emptyStateIcon('heroicon-o-document-text')\n ->emptyStateActions([\n Action::make('create')\n ->label('Create Post')\n ->url(fn (): string => route('posts.create'))\n ->icon('heroicon-m-plus'),\n ])\n ->filtersTriggerAction(fn (Action $action) => \n $action->button()->label('Filters')\n )\n ->filtersFormColumns(2)\n ->filtersFormMaxHeight('400px')\n ->filtersLayout(Tables\\Enums\\FiltersLayout::AboveContent)\n ->groups([\n Group::make('category.name')\n ->titlePrefixedWithLabel(false),\n Group::make('status')\n ->collapsible(),\n ])\n ->defaultGroup('category.name')\n ->groupingSettingsInDropdownOnDesktop()\n ->groupsInDropdownOnDesktop()\n ->groupedTriggerAction(fn (Action $action) => \n $action->button()->label('Group')\n );\n}\n```\n\n## Table Layout Options\n\n```php\n// Content layout\n->contentGrid([\n 'md' => 2,\n 'xl' => 3,\n])\n\n// Filters layout\n->filtersLayout(Tables\\Enums\\FiltersLayout::AboveContent)\n->filtersLayout(Tables\\Enums\\FiltersLayout::AboveContentCollapsible)\n->filtersLayout(Tables\\Enums\\FiltersLayout::Modal)\n\n// Actions layout\n->actionsAlignment(Alignment::Left)\n->actionsAlignment(Alignment::Right)\n->actionsAlignment(Alignment::Center)\n->actionsColumnLabel('Actions')\n```\n\n## Eloquent Query Customization\n\n```php\npublic static function getEloquentQuery(): Builder\n{\n return parent::getEloquentQuery()\n ->with(['author', 'category']) // Eager load\n ->where('status', '!=', 'archived'); // Default filter\n}\n```\n\n## Selection and Records per Page\n\n```php\n// Enable record selection\n->selectable()\n->selectCurrentPageOnly()\n\n// Pagination options\n->paginated([10, 25, 50, 100, 'all'])\n->defaultPaginationPageOption(25)\n->extremePaginationLinks() // Show first/last page links\n\n// Simple pagination\n->pagination(false)\n```\n\n## Complete Example: Product Table\n\n```php\npublic static function table(Table $table): Table\n{\n return $table\n ->columns([\n Tables\\Columns\\ImageColumn::make('image')\n ->square()\n ->size(50),\n \n Tables\\Columns\\TextColumn::make('name')\n ->searchable()\n ->sortable()\n ->weight('font-bold'),\n \n Tables\\Columns\\TextColumn::make('category.name')\n ->searchable()\n ->sortable(),\n \n Tables\\Columns\\TextColumn::make('price')\n ->money('USD')\n ->sortable()\n ->summarize(Sum::make()->label('Total')),\n \n Tables\\Columns\\TextColumn::make('stock_quantity')\n ->numeric()\n ->sortable()\n ->color(fn (int $state): string => match (true) {\n $state \u003c= 0 => 'danger',\n $state \u003c= 10 => 'warning',\n default => 'success',\n }),\n \n Tables\\Columns\\IconColumn::make('is_active')\n ->boolean(),\n \n Tables\\Columns\\TextColumn::make('created_at')\n ->dateTime()\n ->sortable()\n ->toggleable(isToggledHiddenByDefault: true),\n ])\n ->filters([\n Tables\\Filters\\SelectFilter::make('category')\n ->relationship('category', 'name')\n ->searchable()\n ->preload(),\n \n Tables\\Filters\\Filter::make('low_stock')\n ->label('Low Stock')\n ->query(fn (Builder $query): Builder => $query->where('stock_quantity', '\u003c=', 10))\n ->toggle(),\n \n Tables\\Filters\\Filter::make('price_range')\n ->form([\n Forms\\Components\\TextInput::make('min_price')\n ->numeric()\n ->prefix('

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

),\n Forms\\Components\\TextInput::make('max_price')\n ->numeric()\n ->prefix('

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

),\n ])\n ->query(function (Builder $query, array $data): Builder {\n return $query\n ->when(\n $data['min_price'],\n fn (Builder $query, $price): Builder => $query->where('price', '>=', $price),\n )\n ->when(\n $data['max_price'],\n fn (Builder $query, $price): Builder => $query->where('price', '\u003c=', $price),\n );\n }),\n ])\n ->actions([\n Tables\\Actions\\ViewAction::make(),\n Tables\\Actions\\EditAction::make(),\n \n Tables\\Actions\\Action::make('duplicate')\n ->icon('heroicon-m-document-duplicate')\n ->color('warning')\n ->requiresConfirmation()\n ->action(function (Product $record): void {\n $newProduct = $record->replicate();\n $newProduct->name = $record->name . ' (Copy)';\n $newProduct->sku = $record->sku . '-COPY';\n $newProduct->save();\n }),\n \n Tables\\Actions\\DeleteAction::make(),\n ])\n ->bulkActions([\n Tables\\Actions\\BulkActionGroup::make([\n Tables\\Actions\\DeleteBulkAction::make(),\n \n Tables\\Actions\\BulkAction::make('updateStock')\n ->icon('heroicon-m-archive-box')\n ->form([\n Forms\\Components\\TextInput::make('quantity')\n ->numeric()\n ->required(),\n ])\n ->action(function ($records, array $data): void {\n foreach ($records as $record) {\n $record->increment('stock_quantity', $data['quantity']);\n }\n }),\n \n Tables\\Actions\\BulkAction::make('activate')\n ->icon('heroicon-m-check-circle')\n ->color('success')\n ->action(fn ($records) => $records->each->update(['is_active' => true])),\n ]),\n ])\n ->defaultSort('created_at', 'desc')\n ->poll('30s');\n}\n```\n\n## Tips & Best Practices\n\n1. **Use eager loading** in `getEloquentQuery()` to avoid N+1\n2. **Add searchable()** to frequently filtered columns\n3. **Use toggleable()** for less important columns\n4. **Implement summarizers** for numeric data\n5. **Use color()** on status columns for visual feedback\n6. **Add record actions** for common operations\n7. **Group related bulk actions** in BulkActionGroup\n8. **Use filters** to help users find data\n9. **Set defaultSort()** for consistent ordering\n10. **Use poll()** for real-time data updates\n11. **Add empty state actions** for better UX\n12. **Format dates consistently** across the table\n13. **Use icon columns** for boolean values\n14. **Add tooltip()** for truncated text\n15. **Test with large datasets** to ensure performance\n\n## Additional Resources\n\n- [Official Tables Documentation](https://filamentphp.com/docs/5.x/tables/columns)\n- [Filters](https://filamentphp.com/docs/5.x/tables/filters)\n- [Actions](https://filamentphp.com/docs/5.x/tables/actions)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":20029,"content_sha256":"e902e9aba3ce8ab87808bdb842bf32e55a005ed748847a6ae2d5ece3b0f88884"},{"filename":"references/testing.md","content":"# Testing Reference\n\nComplete guide for testing Filament v5 resources, forms, and tables with Pest PHP.\n\n## Setup\n\n```bash\n# Install Pest\ncomposer require pestphp/pest --dev\n\n# Install Livewire testing plugin\ncomposer require pestphp/pest-plugin-livewire --dev\n\n# Install Filament testing helpers (if separate package)\ncomposer require filament/testing --dev\n```\n\n## Basic Test Structure\n\n```php\n\u003c?php\n\nuse App\\Filament\\Resources\\PostResource;\nuse App\\Filament\\Resources\\PostResource\\Pages\\CreatePost;\nuse App\\Filament\\Resources\\PostResource\\Pages\\EditPost;\nuse App\\Filament\\Resources\\PostResource\\Pages\\ListPosts;\nuse App\\Filament\\Resources\\PostResource\\Pages\\ViewPost;\nuse App\\Models\\Post;\nuse App\\Models\\User;\n\nuse function Pest\\Laravel\\actingAs;\nuse function Pest\\Laravel\\assertDatabaseHas;\nuse function Pest\\Laravel\\assertDatabaseMissing;\nuse function Pest\\Livewire\\livewire;\n\nbeforeEach(function () {\n $this->user = User::factory()->create();\n actingAs($this->user);\n});\n```\n\n## Testing List Pages\n\n```php\n// Basic page load\ndescribe('List Page', function () {\n it('can render the list page', function () {\n livewire(ListPosts::class)\n ->assertOk();\n });\n\n it('can list posts', function () {\n $posts = Post::factory()->count(5)->create();\n\n livewire(ListPosts::class)\n ->assertCanSeeTableRecords($posts);\n });\n\n it('can search posts by title', function () {\n Post::factory()->create(['title' => 'Hello World']);\n Post::factory()->create(['title' => 'Another Post']);\n\n livewire(ListPosts::class)\n ->searchTable('Hello')\n ->assertCanSeeTableRecords(Post::where('title', 'like', '%Hello%')->get())\n ->assertCanNotSeeTableRecords(Post::where('title', 'not like', '%Hello%')->get());\n });\n\n it('can sort posts', function () {\n Post::factory()->create(['title' => 'C Post']);\n Post::factory()->create(['title' => 'A Post']);\n Post::factory()->create(['title' => 'B Post']);\n\n livewire(ListPosts::class)\n ->sortTable('title')\n ->assertCanSeeTableRecords(Post::orderBy('title')->get(), inOrder: true);\n });\n\n it('can filter posts by status', function () {\n Post::factory()->create(['status' => 'published']);\n Post::factory()->create(['status' => 'draft']);\n\n livewire(ListPosts::class)\n ->filterTable('status', 'published')\n ->assertCanSeeTableRecords(Post::where('status', 'published')->get())\n ->assertCanNotSeeTableRecords(Post::where('status', '!=', 'published')->get());\n });\n\n it('can filter posts with multiple statuses', function () {\n Post::factory()->create(['status' => 'published']);\n Post::factory()->create(['status' => 'draft']);\n Post::factory()->create(['status' => 'archived']);\n\n livewire(ListPosts::class)\n ->filterTable('status', ['published', 'draft'])\n ->assertCanSeeTableRecords(Post::whereIn('status', ['published', 'draft'])->get())\n ->assertCanNotSeeTableRecords(Post::where('status', 'archived')->get());\n });\n});\n```\n\n## Testing Table Columns\n\n```php\nit('can render table columns', function () {\n livewire(ListPosts::class)\n ->assertCanRenderTableColumn('title')\n ->assertCanRenderTableColumn('status')\n ->assertCanRenderTableColumn('created_at')\n ->assertCanNotRenderTableColumn('password'); // Hidden column\n});\n\nit('can sort by date column', function () {\n $posts = Post::factory()->count(3)->create();\n\n livewire(ListPosts::class)\n ->sortTable('created_at')\n ->assertCanSeeTableRecords($posts->sortBy('created_at'), inOrder: true)\n ->sortTable('created_at', 'desc')\n ->assertCanSeeTableRecords($posts->sortByDesc('created_at'), inOrder: true);\n});\n\nit('can hide columns', function () {\n livewire(ListPosts::class)\n ->assertTableColumnVisible('title')\n ->assertTableColumnHidden('deleted_at');\n});\n```\n\n## Testing Table Actions\n\n```php\n// Single record actions\nit('can delete a post', function () {\n $post = Post::factory()->create();\n\n livewire(ListPosts::class)\n ->callTableAction('delete', $post)\n ->assertNotified()\n ->assertCanNotSeeTableRecords([$post]);\n\n assertDatabaseMissing('posts', ['id' => $post->id]);\n});\n\nit('can edit a post from list', function () {\n $post = Post::factory()->create();\n\n livewire(ListPosts::class)\n ->callTableAction('edit', $post)\n ->assertRedirect(PostResource::getUrl('edit', ['record' => $post]));\n});\n\n// Bulk actions\nit('can bulk delete posts', function () {\n $posts = Post::factory()->count(3)->create();\n\n livewire(ListPosts::class)\n ->selectTableRecords($posts)\n ->callTableBulkAction('delete', $posts)\n ->assertNotified()\n ->assertCanNotSeeTableRecords($posts);\n\n $posts->each(fn ($post) => assertDatabaseMissing('posts', ['id' => $post->id]));\n});\n\nit('can bulk update status', function () {\n $posts = Post::factory()->count(3)->create(['status' => 'draft']);\n\n livewire(ListPosts::class)\n ->selectTableRecords($posts)\n ->callTableBulkAction('updateStatus', $posts, data: ['status' => 'published'])\n ->assertNotified()\n ->assertHasNoTableBulkActionErrors();\n\n $posts->each->refresh();\n expect($posts)->each->status->toBe('published');\n});\n\n// Custom actions\nit('can publish a post', function () {\n $post = Post::factory()->create(['status' => 'draft']);\n\n livewire(ListPosts::class)\n ->callTableAction('publish', $post)\n ->assertNotified();\n\n expect($post->fresh()->status)->toBe('published');\n});\n\nit('can duplicate a post', function () {\n $post = Post::factory()->create(['title' => 'Original']);\n\n livewire(ListPosts::class)\n ->callTableAction('duplicate', $post)\n ->assertNotified();\n\n assertDatabaseHas('posts', [\n 'title' => 'Original (Copy)',\n 'slug' => $post->slug . '-copy',\n ]);\n});\n```\n\n## Testing Create Pages\n\n```php\ndescribe('Create Page', function () {\n it('can render the create page', function () {\n livewire(CreatePost::class)\n ->assertOk();\n });\n\n it('can create a post', function () {\n $newData = Post::factory()->make();\n\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => $newData->title,\n 'content' => $newData->content,\n 'status' => $newData->status,\n ])\n ->call('create')\n ->assertNotified()\n ->assertRedirect(PostResource::getUrl('index'));\n\n assertDatabaseHas('posts', [\n 'title' => $newData->title,\n 'content' => $newData->content,\n 'status' => $newData->status,\n 'user_id' => $this->user->id,\n ]);\n });\n\n it('validates required fields', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => null,\n 'content' => null,\n ])\n ->call('create')\n ->assertHasFormErrors(['title' => 'required', 'content' => 'required'])\n ->assertNotNotified();\n });\n\n it('validates email format', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => 'Test',\n 'content' => 'Content',\n 'author_email' => 'invalid-email',\n ])\n ->call('create')\n ->assertHasFormErrors(['author_email' => 'email']);\n });\n\n it('validates unique fields', function () {\n $existing = Post::factory()->create();\n\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => 'Test',\n 'slug' => $existing->slug, // Must be unique\n ])\n ->call('create')\n ->assertHasFormErrors(['slug' => 'unique']);\n });\n\n it('validates max length', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => str_repeat('a', 256), // Max 255\n ])\n ->call('create')\n ->assertHasFormErrors(['title' => 'max']);\n });\n\n it('validates min length', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => 'ab', // Min 3\n ])\n ->call('create')\n ->assertHasFormErrors(['title' => 'min']);\n });\n});\n```\n\n## Testing Edit Pages\n\n```php\ndescribe('Edit Page', function () {\n it('can render the edit page', function () {\n $post = Post::factory()->create();\n\n livewire(EditPost::class, ['record' => $post->id])\n ->assertOk();\n });\n\n it('can retrieve post data', function () {\n $post = Post::factory()->create();\n\n livewire(EditPost::class, ['record' => $post->id])\n ->assertFormSet([\n 'title' => $post->title,\n 'content' => $post->content,\n 'status' => $post->status,\n ]);\n });\n\n it('can update a post', function () {\n $post = Post::factory()->create();\n $newData = Post::factory()->make();\n\n livewire(EditPost::class, ['record' => $post->id])\n ->fillForm([\n 'title' => $newData->title,\n 'content' => $newData->content,\n ])\n ->call('save')\n ->assertNotified()\n ->assertRedirect(PostResource::getUrl('index'));\n\n assertDatabaseHas('posts', [\n 'id' => $post->id,\n 'title' => $newData->title,\n 'content' => $newData->content,\n ]);\n });\n\n it('can delete a post from edit page', function () {\n $post = Post::factory()->create();\n\n livewire(EditPost::class, ['record' => $post->id])\n ->callAction('delete')\n ->assertNotified()\n ->assertRedirect(PostResource::getUrl('index'));\n\n assertDatabaseMissing('posts', ['id' => $post->id]);\n });\n\n it('cannot edit posts owned by other users', function () {\n $otherUser = User::factory()->create();\n $post = Post::factory()->create(['user_id' => $otherUser->id]);\n\n livewire(EditPost::class, ['record' => $post->id])\n ->assertForbidden();\n });\n});\n```\n\n## Testing View Pages\n\n```php\ndescribe('View Page', function () {\n it('can render the view page', function () {\n $post = Post::factory()->create();\n\n livewire(ViewPost::class, ['record' => $post->id])\n ->assertOk();\n });\n\n it('can view post details', function () {\n $post = Post::factory()->create();\n\n livewire(ViewPost::class, ['record' => $post->id])\n ->assertInfolistSet([\n 'title' => $post->title,\n 'content' => $post->content,\n ]);\n });\n\n it('can navigate to edit from view', function () {\n $post = Post::factory()->create();\n\n livewire(ViewPost::class, ['record' => $post->id])\n ->callAction('edit')\n ->assertRedirect(PostResource::getUrl('edit', ['record' => $post]));\n });\n});\n```\n\n## Testing Form Components\n\n```php\nit('can fill form fields', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => 'My Title',\n 'content' => 'My Content',\n 'is_featured' => true,\n ])\n ->assertFormSet([\n 'title' => 'My Title',\n 'content' => 'My Content',\n 'is_featured' => true,\n ]);\n});\n\nit('can test select field', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'status' => 'published',\n ])\n ->assertHasNoFormErrors();\n});\n\nit('can test date picker', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'published_at' => now()->format('Y-m-d H:i:s'),\n ])\n ->assertHasNoFormErrors();\n});\n\nit('can test file upload', function () {\n Storage::fake('public');\n \n $file = UploadedFile::fake()->image('featured.jpg');\n \n livewire(CreatePost::class)\n ->fillForm([\n 'title' => 'Test',\n 'featured_image' => [$file],\n ])\n ->call('create')\n ->assertHasNoFormErrors();\n \n Storage::disk('public')->assertExists('posts/' . $file->hashName());\n});\n\nit('can test repeater field', function () {\n livewire(CreatePost::class)\n ->fillForm([\n 'items' => [\n ['name' => 'Item 1', 'quantity' => 2],\n ['name' => 'Item 2', 'quantity' => 3],\n ],\n ])\n ->assertHasNoFormErrors();\n});\n```\n\n## Testing Modal Actions\n\n```php\nit('can trigger modal action', function () {\n $post = Post::factory()->create();\n\n livewire(EditPost::class, ['record' => $post->id])\n ->callAction('sendEmail')\n ->assertActionHalted('sendEmail'); // Modal is open\n});\n\nit('can fill modal form and submit', function () {\n $post = Post::factory()->create();\n\n livewire(EditPost::class, ['record' => $post->id])\n ->callAction('sendEmail')\n ->assertActionHalted('sendEmail')\n ->fillForm([\n 'subject' => 'Hello',\n 'body' => 'Message body',\n ], component: 'sendEmail')\n ->callMountedAction()\n ->assertHasNoActionErrors()\n ->assertNotified();\n});\n\nit('validates modal form fields', function () {\n $post = Post::factory()->create();\n\n livewire(EditPost::class, ['record' => $post->id])\n ->callAction('sendEmail')\n ->fillForm([\n 'subject' => '', // Required\n ], component: 'sendEmail')\n ->callMountedAction()\n ->assertHasActionErrors(['subject' => 'required']);\n});\n```\n\n## Testing Notifications\n\n```php\nit('shows success notification after create', function () {\n $data = Post::factory()->make();\n\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => $data->title,\n 'content' => $data->content,\n ])\n ->call('create')\n ->assertNotified('Post created successfully');\n});\n\nit('shows custom notification message', function () {\n livewire(CreatePost::class)\n ->fillForm(['title' => ''])\n ->call('create')\n ->assertNotified(\n Notification::make()\n ->title('Error')\n ->body('Please fill all required fields')\n ->danger()\n );\n});\n```\n\n## Testing Authorization\n\n```php\nit('denies access to guests', function () {\n auth()->logout();\n\n livewire(ListPosts::class)\n ->assertRedirect('/login');\n});\n\nit('denies access to unauthorized users', function () {\n $user = User::factory()->create(['role' => 'user']);\n actingAs($user);\n\n livewire(ListPosts::class)\n ->assertForbidden();\n});\n\nit('allows access to authorized users', function () {\n $admin = User::factory()->create(['role' => 'admin']);\n actingAs($admin);\n\n livewire(ListPosts::class)\n ->assertOk();\n});\n\nit('cannot delete posts without permission', function () {\n $user = User::factory()->create();\n $user->revokePermissionTo('delete posts');\n actingAs($user);\n\n $post = Post::factory()->create();\n\n livewire(ListPosts::class)\n ->assertTableActionHidden('delete', $post);\n});\n```\n\n## Testing Multi-Tenancy\n\n```php\nuse Filament\\Facades\\Filament;\n\nbeforeEach(function () {\n $this->user = User::factory()->create();\n $this->team = Team::factory()->create();\n $this->user->teams()->attach($this->team);\n \n actingAs($this->user);\n Filament::setTenant($this->team);\n});\n\nit('shows only tenant posts', function () {\n $teamPost = Post::factory()->create(['team_id' => $this->team->id]);\n $otherPost = Post::factory()->create(['team_id' => Team::factory()->create()->id]);\n\n livewire(ListPosts::class)\n ->assertCanSeeTableRecords([$teamPost])\n ->assertCanNotSeeTableRecords([$otherPost]);\n});\n\nit('automatically sets tenant on create', function () {\n $newData = Post::factory()->make();\n\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => $newData->title,\n 'content' => $newData->content,\n ])\n ->call('create')\n ->assertNotified();\n\n $this->assertDatabaseHas('posts', [\n 'title' => $newData->title,\n 'team_id' => $this->team->id,\n ]);\n});\n```\n\n## Testing with Datasets\n\n```php\nit('validates input correctly', function (array $data, array $errors) {\n $newData = Post::factory()->make();\n\n livewire(CreatePost::class)\n ->fillForm([\n 'title' => $newData->title,\n 'content' => $newData->content,\n ...$data,\n ])\n ->call('create')\n ->assertHasFormErrors($errors);\n})->with([\n 'title is required' => [['title' => null], ['title' => 'required']],\n 'title is max 255 characters' => [['title' => str_repeat('a', 256)], ['title' => 'max']],\n 'email is valid' => [['author_email' => 'invalid'], ['author_email' => 'email']],\n 'status is required' => [['status' => null], ['status' => 'required']],\n]);\n\nit('filters by status correctly', function (string $status, int $expectedCount) {\n Post::factory()->count(3)->create(['status' => 'published']);\n Post::factory()->count(2)->create(['status' => 'draft']);\n\n livewire(ListPosts::class)\n ->filterTable('status', $status)\n ->assertCanSeeTableRecords(Post::where('status', $status)->get()->slice(0, $expectedCount));\n})->with([\n 'published' => ['published', 3],\n 'draft' => ['draft', 2],\n]);\n```\n\n## Testing Custom Pages\n\n```php\nuse App\\Filament\\Pages\\Dashboard;\nuse App\\Filament\\Pages\\Settings;\n\nit('can access dashboard', function () {\n livewire(Dashboard::class)\n ->assertOk();\n});\n\nit('can access settings page', function () {\n $admin = User::factory()->create(['is_admin' => true]);\n actingAs($admin);\n\n livewire(Settings::class)\n ->assertOk();\n});\n\nit('can save settings', function () {\n $admin = User::factory()->create(['is_admin' => true]);\n actingAs($admin);\n\n livewire(Settings::class)\n ->fillForm([\n 'data.site_name' => 'New Site Name',\n ])\n ->call('save')\n ->assertNotified();\n\n expect(setting('site_name'))->toBe('New Site Name');\n});\n```\n\n## Testing Widgets\n\n```php\nuse App\\Filament\\Widgets\\StatsOverview;\nuse App\\Filament\\Widgets\\OrdersChart;\n\nit('displays stats correctly', function () {\n User::factory()->count(5)->create();\n\n livewire(StatsOverview::class)\n ->assertSee('5'); // User count\n});\n\nit('displays chart data', function () {\n Order::factory()->count(10)->create();\n\n livewire(OrdersChart::class)\n ->assertOk();\n});\n```\n\n## Testing Relation Managers\n\n```php\nuse App\\Filament\\Resources\\PostResource\\RelationManagers\\CommentsRelationManager;\n\nit('can render relation manager', function () {\n $post = Post::factory()->create();\n\n livewire(CommentsRelationManager::class, [\n 'ownerRecord' => $post,\n 'pageClass' => EditPost::class,\n ])\n ->assertOk();\n});\n\nit('can create related record', function () {\n $post = Post::factory()->create();\n\n livewire(CommentsRelationManager::class, [\n 'ownerRecord' => $post,\n 'pageClass' => EditPost::class,\n ])\n ->callTableAction('create')\n ->assertTableActionHalted('create')\n ->fillForm([\n 'content' => 'Test comment',\n ])\n ->callMountedTableAction()\n ->assertHasNoTableActionErrors();\n\n assertDatabaseHas('comments', [\n 'post_id' => $post->id,\n 'content' => 'Test comment',\n ]);\n});\n```\n\n## Best Practices\n\n1. **Test all CRUD operations** - Create, Read, Update, Delete\n2. **Test validation rules** - Required, format, unique, etc.\n3. **Test authorization** - Access control and permissions\n4. **Test table features** - Search, sort, filter, actions\n5. **Test edge cases** - Empty data, large datasets, errors\n6. **Use factories** - Create realistic test data\n7. **Group related tests** - Use describe() blocks\n8. **Use datasets** - Test multiple scenarios efficiently\n9. **Test notifications** - Verify user feedback\n10. **Test redirects** - After create/update/delete\n11. **Test file uploads** - With storage fakes\n12. **Test multi-tenancy** - Scoped data access\n13. **Keep tests fast** - Avoid unnecessary DB calls\n14. **Use beforeEach** - Set up common state\n15. **Assert on database** - Verify state changes\n\n## Helper Methods Reference\n\n### Form Testing\n- `fillForm(array $data)` - Fill form fields\n- `assertFormSet(array $data)` - Assert form values\n- `assertHasFormErrors(array $errors)` - Assert validation errors\n- `assertHasNoFormErrors()` - Assert no errors\n- `call(string $method)` - Call form method (e.g., 'create', 'save')\n\n### Table Testing\n- `assertCanSeeTableRecords($records)` - Assert records visible\n- `assertCanNotSeeTableRecords($records)` - Assert records hidden\n- `searchTable(string $query)` - Search table\n- `sortTable(string $column, string $direction = 'asc')` - Sort table\n- `filterTable(string $filter, $value)` - Apply filter\n- `selectTableRecords($records)` - Select records for bulk actions\n- `callTableAction(string $action, $record)` - Call row action\n- `callTableBulkAction(string $action, $records)` - Call bulk action\n- `assertTableColumnVisible(string $column)` - Assert column visible\n- `assertTableColumnHidden(string $column)` - Assert column hidden\n\n### Action Testing\n- `callAction(string $action)` - Call page action\n- `assertActionHalted(string $action)` - Assert modal opened\n- `callMountedAction()` - Submit modal form\n- `assertHasActionErrors(array $errors)` - Assert modal errors\n- `assertHasNoActionErrors()` - Assert no modal errors\n\n### Notification Testing\n- `assertNotified(string $message = null)` - Assert notification shown\n- `assertNotNotified()` - Assert no notification\n\n### Authorization Testing\n- `assertOk()` - Assert 200 status\n- `assertForbidden()` - Assert 403 status\n- `assertRedirect(string $url)` - Assert redirect\n\n## Additional Resources\n\n- [Pest PHP Documentation](https://pestphp.com/)\n- [Livewire Testing](https://livewire.laravel.com/docs/testing)\n- [Filament Testing](https://filamentphp.com/docs/5.x/testing/overview)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":22320,"content_sha256":"caa5163f927743b92f962111dd6abb596a77564e74913c5c251e8dace18aaec8"},{"filename":"references/widgets.md","content":"# Widgets Reference\n\nComplete guide for creating dashboard widgets in Filament v5.\n\n## Creating Widgets\n\n```bash\n# Stats overview widget\nphp artisan make:filament-widget StatsOverview --stats-overview\n\n# Chart widget\nphp artisan make:filament-widget BlogPostsChart --chart\n\n# Table widget\nphp artisan make:filament-widget LatestOrders --table\n\n# Custom widget\nphp artisan make:filament-widget CustomWidget\n```\n\n## Widget Types\n\n| Type | Command | Description |\n|------|---------|-------------|\n| Stats Overview | `--stats-overview` | Display statistics cards |\n| Chart | `--chart` | Display Chart.js charts |\n| Table | `--table` | Display data tables |\n| Custom | (none) | Custom Livewire component |\n\n## Stats Overview Widget\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Widgets;\n\nuse App\\Models\\Order;\nuse App\\Models\\User;\nuse Filament\\Widgets\\StatsOverviewWidget;\nuse Filament\\Widgets\\StatsOverviewWidget\\Stat;\n\nclass StatsOverview extends StatsOverviewWidget\n{\n // Widget configuration\n protected static ?string $heading = 'Dashboard Overview';\n protected ?string $description = 'Key metrics at a glance';\n \n // Layout\n protected function getColumns(): int\n {\n return 3;\n }\n \n protected function getStats(): array\n {\n return [\n Stat::make('Total Users', User::count())\n ->description(User::where('created_at', '>=', now()->subDays(30))->count() . ' this month')\n ->descriptionIcon('heroicon-m-arrow-trending-up')\n ->chart([7, 3, 4, 5, 6, 3, 5, 8])\n ->color('success'),\n \n Stat::make('Total Orders', Order::count())\n ->description(Order::where('created_at', '>=', now()->subDays(7))->count() . ' this week')\n ->color('primary'),\n \n Stat::make('Revenue', '

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

. number_format(Order::sum('total'), 2))\n ->description(Order::where('created_at', '>=', now()->subDays(30))->sum('total') > Order::whereBetween('created_at', [now()->subDays(60), now()->subDays(30)])->sum('total') ? 'Increase' : 'Decrease')\n ->descriptionIcon(fn () => Order::where('created_at', '>=', now()->subDays(30))->sum('total') > Order::whereBetween('created_at', [now()->subDays(60), now()->subDays(30)])->sum('total') ? 'heroicon-m-arrow-trending-up' : 'heroicon-m-arrow-trending-down')\n ->color(fn () => Order::where('created_at', '>=', now()->subDays(30))->sum('total') > Order::whereBetween('created_at', [now()->subDays(60), now()->subDays(30)])->sum('total') ? 'success' : 'danger'),\n \n Stat::make('Pending Orders', Order::where('status', 'pending')->count())\n ->color('warning')\n ->url(OrderResource::getUrl('index', ['tableFilters[status][value]' => 'pending'])),\n ];\n }\n}\n```\n\n### Stat Options\n\n```php\nStat::make('Label', 'value')\n ->description('Description text')\n ->descriptionIcon('heroicon-m-arrow-trending-up')\n ->icon('heroicon-m-users')\n ->chart([7, 2, 10, 3, 15, 4, 17]) // Sparkline chart\n ->color('success') // success, danger, warning, primary, secondary\n ->url('/admin/orders') // Clickable link\n ->extraAttributes(['class' => 'col-span-2'])\n```\n\n### Dynamic Stats with Database Data\n\n```php\nprotected function getStats(): array\n{\n $newUsersThisMonth = User::where('created_at', '>=', now()->subDays(30))->count();\n $newUsersLastMonth = User::whereBetween('created_at', [now()->subDays(60), now()->subDays(30)])->count();\n \n $userGrowth = $newUsersLastMonth > 0 \n ? round((($newUsersThisMonth - $newUsersLastMonth) / $newUsersLastMonth) * 100, 1)\n : 0;\n \n return [\n Stat::make('New Users (30 days)', $newUsersThisMonth)\n ->description($userGrowth > 0 ? \"+{$userGrowth}% increase\" : \"{$userGrowth}% decrease\")\n ->descriptionIcon($userGrowth > 0 ? 'heroicon-m-arrow-trending-up' : 'heroicon-m-arrow-trending-down')\n ->color($userGrowth > 0 ? 'success' : 'danger'),\n ];\n}\n```\n\n## Chart Widget\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Widgets;\n\nuse App\\Models\\Order;\nuse Filament\\Widgets\\ChartWidget;\n\nclass OrdersChart extends ChartWidget\n{\n protected static ?string $heading = 'Orders per Month';\n protected ?string $description = 'Monthly order volume';\n protected static string $color = 'primary'; // primary, success, danger, warning, info, gray\n protected static ?string $maxHeight = '300px';\n \n protected function getData(): array\n {\n $orders = Order::query()\n ->selectRaw('MONTH(created_at) as month, COUNT(*) as count')\n ->whereYear('created_at', now()->year)\n ->groupBy('month')\n ->pluck('count', 'month')\n ->toArray();\n \n return [\n 'datasets' => [\n [\n 'label' => 'Orders',\n 'data' => array_values($orders),\n 'backgroundColor' => '#f59e0b',\n 'borderColor' => '#d97706',\n 'fill' => true,\n 'tension' => 0.3,\n ],\n ],\n 'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],\n ];\n }\n \n protected function getType(): string\n {\n return 'line'; // line, bar, pie, doughnut, polarArea, radar, bubble, scatter\n }\n \n protected function getOptions(): array\n {\n return [\n 'scales' => [\n 'y' => [\n 'beginAtZero' => true,\n 'ticks' => [\n 'precision' => 0,\n ],\n ],\n ],\n 'plugins' => [\n 'legend' => [\n 'display' => true,\n ],\n ],\n ];\n }\n \n // Optional: Add time range filters\n protected function getFilters(): ?array\n {\n return [\n 'today' => 'Today',\n 'week' => 'Last week',\n 'month' => 'Last month',\n 'year' => 'This year',\n ];\n }\n \n protected function getDataForFilter(string $filter): array\n {\n return match ($filter) {\n 'today' => $this->getTodayData(),\n 'week' => $this->getWeekData(),\n 'month' => $this->getMonthData(),\n 'year' => $this->getYearData(),\n default => $this->getYearData(),\n };\n }\n}\n```\n\n### Chart Types\n\n```php\n// Line chart\nprotected function getType(): string\n{\n return 'line';\n}\n\n// Bar chart\nprotected function getType(): string\n{\n return 'bar';\n}\n\n// Pie chart\nprotected function getType(): string\n{\n return 'pie';\n}\n\n// Doughnut chart\nprotected function getType(): string\n{\n return 'doughnut';\n}\n```\n\n### Chart with Trend Data\n\n```php\nuse Flowframe\\Trend\\Trend;\nuse Flowframe\\Trend\\TrendValue;\n\nprotected function getData(): array\n{\n $trend = Trend::model(Order::class)\n ->between(\n start: now()->startOfYear(),\n end: now()->endOfYear(),\n )\n ->perMonth()\n ->count();\n \n return [\n 'datasets' => [\n [\n 'label' => 'Orders',\n 'data' => $trend->map(fn (TrendValue $value) => $value->aggregate),\n 'backgroundColor' => '#36A2EB',\n 'borderColor' => '#9BD0F5',\n ],\n ],\n 'labels' => $trend->map(fn (TrendValue $value) => $value->date),\n ];\n}\n```\n\n### Multiple Datasets\n\n```php\nprotected function getData(): array\n{\n return [\n 'datasets' => [\n [\n 'label' => 'Revenue',\n 'data' => [1000, 1500, 1200, 2000, 1800, 2500],\n 'backgroundColor' => '#22c55e',\n 'borderColor' => '#16a34a',\n ],\n [\n 'label' => 'Expenses',\n 'data' => [800, 900, 1000, 1100, 1200, 1300],\n 'backgroundColor' => '#ef4444',\n 'borderColor' => '#dc2626',\n ],\n ],\n 'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],\n ];\n}\n```\n\n## Table Widget\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Widgets;\n\nuse App\\Filament\\Resources\\OrderResource;\nuse App\\Models\\Order;\nuse Filament\\Tables;\nuse Filament\\Tables\\Table;\nuse Filament\\Widgets\\TableWidget;\n\nclass LatestOrders extends TableWidget\n{\n protected int | string | array $columnSpan = 'full';\n protected static ?string $heading = 'Latest Orders';\n protected ?string $description = 'Most recent orders';\n protected static ?int $paginationPageSize = 5;\n \n public function table(Table $table): Table\n {\n return $table\n ->query(\n Order::query()\n ->latest()\n ->limit(10)\n )\n ->columns([\n Tables\\Columns\\TextColumn::make('order_number')\n ->searchable(),\n \n Tables\\Columns\\TextColumn::make('customer.name')\n ->searchable(),\n \n Tables\\Columns\\TextColumn::make('total')\n ->money('USD'),\n \n Tables\\Columns\\TextColumn::make('status')\n ->badge()\n ->color(fn (string $state): string => match ($state) {\n 'pending' => 'warning',\n 'processing' => 'info',\n 'completed' => 'success',\n 'cancelled' => 'danger',\n default => 'gray',\n }),\n \n Tables\\Columns\\TextColumn::make('created_at')\n ->dateTime()\n ->since(),\n ])\n ->actions([\n Tables\\Actions\\ViewAction::make()\n ->url(fn (Order $record): string => OrderResource::getUrl('view', ['record' => $record])),\n ])\n ->paginated([5, 10, 25]);\n }\n}\n```\n\n### Table Widget with Filters\n\n```php\npublic function table(Table $table): Table\n{\n return $table\n ->query(\n Order::query()\n ->when(\n $this->filter === 'pending',\n fn ($query) => $query->where('status', 'pending')\n )\n ->when(\n $this->filter === 'completed',\n fn ($query) => $query->where('status', 'completed')\n )\n ->latest()\n )\n ->columns([\n // ... columns\n ]);\n}\n\npublic ?string $filter = 'all';\n\nprotected function getTableFilters(): array\n{\n return [\n 'all' => 'All Orders',\n 'pending' => 'Pending',\n 'completed' => 'Completed',\n ];\n}\n```\n\n## Custom Widget\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Widgets;\n\nuse Filament\\Widgets\\Widget;\n\nclass CustomWidget extends Widget\n{\n protected static string $view = 'filament.widgets.custom-widget';\n \n protected function getViewData(): array\n {\n return [\n 'data' => $this->getData(),\n ];\n }\n \n protected function getData(): array\n {\n return [\n 'total' => 100,\n 'increase' => 20,\n ];\n }\n}\n```\n\n### Custom Widget Blade Template\n\n```blade\n{{-- resources/views/filament/widgets/custom-widget.blade.php --}}\n\u003cx-filament-widgets::widget>\n \u003cx-filament::card>\n \u003cdiv class=\"flex items-center justify-between\">\n \u003cdiv>\n \u003ch3 class=\"text-lg font-medium\">Custom Metric\u003c/h3>\n \u003cp class=\"text-3xl font-bold\">{{ $data['total'] }}\u003c/p>\n \u003cp class=\"text-sm text-green-600\">+{{ $data['increase'] }}%\u003c/p>\n \u003c/div>\n \u003cdiv class=\"p-3 bg-primary-100 rounded-full\">\n \u003cx-heroicon-o-chart-bar class=\"w-6 h-6 text-primary-600\" />\n \u003c/div>\n \u003c/div>\n \u003c/x-filament::card>\n\u003c/x-filament-widgets::widget>\n```\n\n## Widget Configuration\n\n### Column Span\n\nControl widget width on the dashboard:\n\n```php\n// Full width\nprotected int | string | array $columnSpan = 'full';\n\n// Two columns\nprotected int | string | array $columnSpan = 2;\n\n// Responsive column span\nprotected int | string | array $columnSpan = [\n 'md' => 2,\n 'xl' => 3,\n];\n```\n\n### Sort Order\n\n```php\nprotected static ?int $sort = 2; // Lower numbers appear first\n```\n\n### Visibility\n\n```php\npublic static function canView(): bool\n{\n return auth()->user()->isAdmin();\n}\n```\n\n### Heading and Description\n\n```php\nprotected static ?string $heading = 'Dashboard Stats';\nprotected ?string $description = 'Overview of key metrics';\n```\n\n### Polling\n\nAuto-refresh widget data:\n\n```php\nprotected static ?string $pollingInterval = '30s'; // 30 seconds\nprotected static ?string $pollingInterval = '1m'; // 1 minute\nprotected static ?string $pollingInterval = null; // Disable polling\n```\n\n## Dashboard Layout\n\nConfigure dashboard in your PanelProvider:\n\n```php\nuse App\\Filament\\Widgets\\StatsOverview;\nuse App\\Filament\\Widgets\\OrdersChart;\nuse App\\Filament\\Widgets\\LatestOrders;\nuse Filament\\Pages\\Dashboard;\n\npublic function panel(Panel $panel): Panel\n{\n return $panel\n // ...\n ->widgets([\n StatsOverview::class,\n OrdersChart::class,\n LatestOrders::class,\n ])\n ->pages([\n Dashboard::class,\n ]);\n}\n```\n\n### Custom Dashboard\n\n```php\n\u003c?php\n\nnamespace App\\Filament\\Pages;\n\nuse Filament\\Pages\\Dashboard as BaseDashboard;\n\nclass Dashboard extends BaseDashboard\n{\n protected static ?string $navigationIcon = 'heroicon-o-home';\n protected static ?string $navigationLabel = 'Dashboard';\n protected static ?int $navigationSort = -2;\n \n // Custom columns\n public function getColumns(): int | array\n {\n return [\n 'default' => 1,\n 'sm' => 2,\n 'md' => 3,\n 'lg' => 4,\n ];\n }\n \n // Filter visible widgets\n public function getWidgets(): array\n {\n return [\n \\App\\Filament\\Widgets\\StatsOverview::class,\n \\App\\Filament\\Widgets\\OrdersChart::class,\n \\App\\Filament\\Widgets\\LatestOrders::class,\n ];\n }\n}\n```\n\n## Complete Example: Dashboard with Multiple Widgets\n\n```php\n\u003c?php\n\n// app/Filament/Widgets/OrderStats.php\nnamespace App\\Filament\\Widgets;\n\nuse App\\Models\\Order;\nuse Filament\\Widgets\\StatsOverviewWidget;\nuse Filament\\Widgets\\StatsOverviewWidget\\Stat;\n\nclass OrderStats extends StatsOverviewWidget\n{\n protected function getStats(): array\n {\n $todayRevenue = Order::whereDate('created_at', today())->sum('total');\n $weekRevenue = Order::where('created_at', '>=', now()->subDays(7))->sum('total');\n $monthRevenue = Order::where('created_at', '>=', now()->subDays(30))->sum('total');\n \n $pendingOrders = Order::where('status', 'pending')->count();\n \n return [\n Stat::make(\"Today's Revenue\", '

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

. number_format($todayRevenue, 2))\n ->description('12 orders today')\n ->color('success'),\n \n Stat::make('This Week', '

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

. number_format($weekRevenue, 2))\n ->description('+15% from last week')\n ->color('primary'),\n \n Stat::make('This Month', '

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

. number_format($monthRevenue, 2))\n ->description('328 total orders')\n ->chart([65, 59, 80, 81, 56, 55, 40])\n ->color('info'),\n \n Stat::make('Pending Orders', $pendingOrders)\n ->description('Require attention')\n ->color('warning')\n ->url('/admin/orders?tableFilters[status][value]=pending'),\n ];\n }\n}\n```\n\n```php\n\u003c?php\n\n// app/Filament/Widgets/RevenueChart.php\nnamespace App\\Filament\\Widgets;\n\nuse App\\Models\\Order;\nuse Filament\\Widgets\\ChartWidget;\n\nclass RevenueChart extends ChartWidget\n{\n protected static ?string $heading = 'Revenue Overview';\n protected static ?int $sort = 2;\n protected int | string | array $columnSpan = 2;\n \n protected function getData(): array\n {\n $revenue = Order::query()\n ->selectRaw('DATE(created_at) as date, SUM(total) as revenue')\n ->where('created_at', '>=', now()->subDays(30))\n ->groupBy('date')\n ->orderBy('date')\n ->pluck('revenue', 'date')\n ->toArray();\n \n return [\n 'datasets' => [\n [\n 'label' => 'Revenue',\n 'data' => array_values($revenue),\n 'backgroundColor' => 'rgba(34, 197, 94, 0.2)',\n 'borderColor' => '#22c55e',\n 'borderWidth' => 2,\n 'fill' => true,\n 'tension' => 0.4,\n ],\n ],\n 'labels' => array_keys($revenue),\n ];\n }\n \n protected function getType(): string\n {\n return 'line';\n }\n \n protected function getOptions(): array\n {\n return [\n 'scales' => [\n 'y' => [\n 'beginAtZero' => true,\n 'ticks' => [\n 'callback' => 'function(value) { return \"$\" + value; }',\n ],\n ],\n ],\n ];\n }\n}\n```\n\n## Best Practices\n\n1. **Keep widgets focused** - One widget per metric type\n2. **Use appropriate chart types** - Line for trends, pie for proportions\n3. **Add context** - Include descriptions and comparisons\n4. **Use colors effectively** - Green for positive, red for negative\n5. **Enable polling** only for real-time data needs\n6. **Limit table widget rows** - Use pagination for large datasets\n7. **Add clickable links** to stats for easy navigation\n8. **Use sparklines** on stats for trend visualization\n9. **Cache expensive queries** - Use Laravel's cache for heavy calculations\n10. **Test on mobile** - Ensure widgets are responsive\n11. **Use columnSpan** effectively - Balance the dashboard layout\n12. **Sort widgets logically** - Most important first\n13. **Add filters** to charts for different time ranges\n14. **Show empty states** - Handle cases with no data gracefully\n15. **Optimize queries** - Use aggregates and avoid N+1\n\n## Tips & Tricks\n\n### Query Optimization\n\n```php\nprotected function getStats(): array\n{\n // Cache expensive queries\n $stats = cache()->remember('dashboard_stats', 300, function () {\n return [\n 'users' => User::count(),\n 'orders' => Order::count(),\n 'revenue' => Order::sum('total'),\n ];\n });\n \n return [\n Stat::make('Users', $stats['users']),\n Stat::make('Orders', $stats['orders']),\n Stat::make('Revenue', '

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…

. number_format($stats['revenue'], 2)),\n ];\n}\n```\n\n### Conditional Display\n\n```php\npublic static function canView(): bool\n{\n return auth()->user()->can('view dashboard stats');\n}\n```\n\n### Dynamic Headings\n\n```php\nprotected function getHeading(): ?string\n{\n return 'Sales: ' . now()->format('F Y');\n}\n```\n\n## Additional Resources\n\n- [Official Widgets Documentation](https://filamentphp.com/docs/5.x/widgets/stats-overview)\n- [Chart.js Documentation](https://www.chartjs.org/docs/)\n- [Trend Package](https://github.com/flowframe/laravel-trend) for time series data\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":19435,"content_sha256":"d1972a73a72eff0203092e843284a3c328bbb96ef558655f81c8f10f08b7ee4a"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Filament v5","type":"text"}]},{"type":"paragraph","content":[{"text":"Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Key Concepts","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PanelProvider","type":"text","marks":[{"type":"strong"}]},{"text":": Central configuration class defining your admin panel","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resources","type":"text","marks":[{"type":"strong"}]},{"text":": Automatic CRUD interfaces for Eloquent models","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Schemas","type":"text","marks":[{"type":"strong"}]},{"text":": Declarative UI components (forms, tables, infolists)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Actions","type":"text","marks":[{"type":"strong"}]},{"text":": Interactive buttons with modals and backend logic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Widgets","type":"text","marks":[{"type":"strong"}]},{"text":": Dashboard components for data visualization","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"System Requirements","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Laravel 11.28+","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PHP 8.2+","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Livewire v4","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Node.js 18+","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tailwind CSS v4.1+","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Installation","type":"text"}]},{"type":"paragraph","content":[{"text":"Install Filament via Composer and scaffold a panel:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"composer require filament/filament:\"^5.0\" -W\nphp artisan filament:install --scaffold\nnpm install && npm run dev\nphp artisan make:filament-user","type":"text"}]},{"type":"paragraph","content":[{"text":"This creates the panel provider, directory structure, and assets needed to start building.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Directory Structure","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"app/\n Filament/\n Resources/ # CRUD resources with forms and tables\n Pages/ # Custom pages\n Widgets/ # Dashboard widgets\n Providers/\n Filament/\n AdminPanelProvider.php","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Concepts","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Panel Configuration","type":"text"}]},{"type":"paragraph","content":[{"text":"The PanelProvider is the entry point for your admin panel. It configures:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identity","type":"text","marks":[{"type":"strong"}]},{"text":": ID, path, branding (name, logo, colors)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Discovery","type":"text","marks":[{"type":"strong"}]},{"text":": Auto-discovery of resources, pages, and widgets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Middleware","type":"text","marks":[{"type":"strong"}]},{"text":": Session, authentication, and custom middleware","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tenancy","type":"text","marks":[{"type":"strong"}]},{"text":": Multi-tenant configuration for SaaS applications","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Resources","type":"text"}]},{"type":"paragraph","content":[{"text":"Resources provide complete CRUD interfaces through:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Forms","type":"text","marks":[{"type":"strong"}]},{"text":": Schema-based forms with 20+ field types (TextInput, Select, DatePicker, FileUpload, RichEditor, etc.)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tables","type":"text","marks":[{"type":"strong"}]},{"text":": Data tables with columns, filters, sorting, and actions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pages","type":"text","marks":[{"type":"strong"}]},{"text":": Automatic generation of List, Create, Edit, and View pages","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Relations","type":"text","marks":[{"type":"strong"}]},{"text":": Relation managers for handling model relationships","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Forms","type":"text"}]},{"type":"paragraph","content":[{"text":"Forms use a schema-based approach where you declare fields as PHP objects:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Input Fields","type":"text","marks":[{"type":"strong"}]},{"text":": Text, select, checkbox, toggle, date/time pickers","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Media","type":"text","marks":[{"type":"strong"}]},{"text":": File and image uploads with validation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Complex Fields","type":"text","marks":[{"type":"strong"}]},{"text":": Rich text editors, repeaters, builders","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Layout","type":"text","marks":[{"type":"strong"}]},{"text":": Grids, sections, tabs, and wizards","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validation","type":"text","marks":[{"type":"strong"}]},{"text":": Built-in Laravel validation rules","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Tables","type":"text"}]},{"type":"paragraph","content":[{"text":"Tables display data with extensive customization:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Columns","type":"text","marks":[{"type":"strong"}]},{"text":": Text, badges, icons, images, colors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Filters","type":"text","marks":[{"type":"strong"}]},{"text":": Select, ternary, and custom filter logic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Actions","type":"text","marks":[{"type":"strong"}]},{"text":": Per-row actions, bulk actions, header actions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Features","type":"text","marks":[{"type":"strong"}]},{"text":": Search, sorting, pagination, grouping","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Actions","type":"text"}]},{"type":"paragraph","content":[{"text":"Actions are interactive buttons that trigger:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Modals","type":"text","marks":[{"type":"strong"}]},{"text":": Form dialogs for data collection","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confirmation","type":"text","marks":[{"type":"strong"}]},{"text":": Destructive action confirmation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wizards","type":"text","marks":[{"type":"strong"}]},{"text":": Multi-step processes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Notifications","type":"text","marks":[{"type":"strong"}]},{"text":": User feedback after completion","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Widgets","type":"text"}]},{"type":"paragraph","content":[{"text":"Dashboard widgets include:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stats Overview","type":"text","marks":[{"type":"strong"}]},{"text":": Metric cards with trends and sparklines","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Charts","type":"text","marks":[{"type":"strong"}]},{"text":": Line, bar, pie charts using Chart.js","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tables","type":"text","marks":[{"type":"strong"}]},{"text":": Data tables for recent records","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing","type":"text"}]},{"type":"paragraph","content":[{"text":"Filament uses Pest PHP with Livewire testing helpers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Page Testing","type":"text","marks":[{"type":"strong"}]},{"text":": List, create, edit, view page functionality","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Form Testing","type":"text","marks":[{"type":"strong"}]},{"text":": Validation, state management, submission","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Table Testing","type":"text","marks":[{"type":"strong"}]},{"text":": Search, filters, sorting, actions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authorization Testing","type":"text","marks":[{"type":"strong"}]},{"text":": Access control and permissions","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Authorization","type":"text"}]},{"type":"paragraph","content":[{"text":"Access control through:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Panel Access","type":"text","marks":[{"type":"strong"}]},{"text":": FilamentUser contract for panel-level access","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Policies","type":"text","marks":[{"type":"strong"}]},{"text":": Laravel policies for resource-level permissions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Field Visibility","type":"text","marks":[{"type":"strong"}]},{"text":": Show/hide fields based on user roles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Multi-Tenancy","type":"text","marks":[{"type":"strong"}]},{"text":": Tenant isolation for SaaS applications","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Architecture Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Server-Driven UI","type":"text"}]},{"type":"paragraph","content":[{"text":"Filament uses a server-driven approach where the backend defines the UI structure through schemas. The PHP code describes forms, tables, and layouts which Filament renders as Livewire components.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Schema System","type":"text"}]},{"type":"paragraph","content":[{"text":"Schemas are PHP configuration objects that define:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Form fields and their validation rules","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Table columns and their formatting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Layout containers (grids, sections, tabs)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Action definitions and their behavior","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Livewire Integration","type":"text"}]},{"type":"paragraph","content":[{"text":"All components mount as Livewire components, providing:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Real-time reactivity without page reloads","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Automatic state management","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Event handling and AJAX updates","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Form validation with instant feedback","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Resource-First Design","type":"text"}]},{"type":"paragraph","content":[{"text":"The framework encourages a resource-first approach:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Define your Eloquent models","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create resources that map to those models","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Configure forms and tables for each resource","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add actions and widgets as needed","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Command Reference","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Command","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Purpose","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"filament:install --scaffold","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Install Filament with panel scaffolding","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"make:filament-resource","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create CRUD resource","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"make:filament-page","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create custom page","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"make:filament-widget","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create dashboard widget","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"make:filament-panel","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create additional panel","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"make:filament-user","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create admin user","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"make:filament-relation-manager","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create relation manager","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"filament:cache-components","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cache for production","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Detailed Documentation","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Reference Guides","type":"text"}]},{"type":"paragraph","content":[{"text":"Comprehensive documentation for each component:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Forms","type":"text","marks":[{"type":"link","attrs":{"href":"references/forms.md","title":null}},{"type":"strong"}]},{"text":" - All form components, validation rules, layouts, and conditional logic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tables","type":"text","marks":[{"type":"link","attrs":{"href":"references/tables.md","title":null}},{"type":"strong"}]},{"text":" - Column types, filters, actions, and table configuration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resources","type":"text","marks":[{"type":"link","attrs":{"href":"references/resources.md","title":null}},{"type":"strong"}]},{"text":" - CRUD resources, relation managers, infolists, and global search","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Infolists","type":"text","marks":[{"type":"link","attrs":{"href":"references/infolists.md","title":null}},{"type":"strong"}]},{"text":" - Read-only data display components (TextEntry, ImageEntry, IconEntry)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Widgets","type":"text","marks":[{"type":"link","attrs":{"href":"references/widgets.md","title":null}},{"type":"strong"}]},{"text":" - Stats overview, charts, and table widgets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Actions","type":"text","marks":[{"type":"link","attrs":{"href":"references/actions.md","title":null}},{"type":"strong"}]},{"text":" - Modal actions, notifications, action groups, and wizards","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Notifications","type":"text","marks":[{"type":"link","attrs":{"href":"references/notifications.md","title":null}},{"type":"strong"}]},{"text":" - Flash messages, database, and broadcast notifications","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Schemas","type":"text","marks":[{"type":"link","attrs":{"href":"references/schemas.md","title":null}},{"type":"strong"}]},{"text":" - Schema system, layouts, and component organization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing","type":"text","marks":[{"type":"link","attrs":{"href":"references/testing.md","title":null}},{"type":"strong"}]},{"text":" - Pest testing patterns for resources, forms, tables, and authorization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authorization","type":"text","marks":[{"type":"link","attrs":{"href":"references/authorization.md","title":null}},{"type":"strong"}]},{"text":" - Access control, policies, roles, and multi-tenancy","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Code Examples","type":"text"}]},{"type":"paragraph","content":[{"text":"See ","type":"text"},{"text":"examples.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/examples.md","title":null}}]},{"text":" for complete working code examples including:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Complete resource implementations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Form configurations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Table setups","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Widget configurations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test suites","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Authorization patterns","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Performance","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"getEloquentQuery()","type":"text","marks":[{"type":"code_inline"}]},{"text":" to eager load relationships and prevent N+1 queries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Enable component caching in production with ","type":"text"},{"text":"filament:cache-components","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Limit pagination options and use deferred loading for large datasets","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cache expensive calculations in widgets","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Security","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always implement the FilamentUser contract for panel access control","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Laravel policies for resource-level authorization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validate all input with appropriate form rules","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Never skip authorization in production environments","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implement proper tenant isolation for multi-tenant applications","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Code Organization","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Organize by feature: ","type":"text"},{"text":"app/Filament/Admin/Resources/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract complex forms and tables to separate classes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create reusable form components for common patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep resources focused on single responsibility","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use dedicated pages for non-CRUD functionality","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test all CRUD operations for each resource","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validate form validation rules with multiple scenarios","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test table features: search, filters, sorting, actions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify authorization with different user roles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use factories to create realistic test data","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use Filament","type":"text"}]},{"type":"paragraph","content":[{"text":"Filament is ideal for:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Admin Panels","type":"text","marks":[{"type":"strong"}]},{"text":": Back-office interfaces for managing application data","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CMS","type":"text","marks":[{"type":"strong"}]},{"text":": Content management systems with rich editing capabilities","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CRM","type":"text","marks":[{"type":"strong"}]},{"text":": Customer relationship management tools","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"E-commerce","type":"text","marks":[{"type":"strong"}]},{"text":": Product, order, and inventory management","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SaaS Applications","type":"text","marks":[{"type":"strong"}]},{"text":": Multi-tenant admin interfaces","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Internal Tools","type":"text","marks":[{"type":"strong"}]},{"text":": Business process management and data entry","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Additional Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Official Documentation","type":"text","marks":[{"type":"link","attrs":{"href":"https://filamentphp.com/docs/5.x","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GitHub Repository","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/filamentphp/filament","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Live Demo","type":"text","marks":[{"type":"link","attrs":{"href":"https://demo.filamentphp.com","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Discord Community","type":"text","marks":[{"type":"link","attrs":{"href":"https://filamentphp.com/discord","title":null}}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Version","type":"text","marks":[{"type":"strong"}]},{"text":": 1.0.0","type":"text"},{"type":"br"},{"text":"License","type":"text","marks":[{"type":"strong"}]},{"text":": MIT","type":"text"},{"type":"br"},{"text":"Compatibility","type":"text","marks":[{"type":"strong"}]},{"text":": Laravel 11+, PHP 8.2+, Livewire v4","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"filament-pro","author":"@skillopedia","source":{"stars":10,"repo_name":"agent-skills","origin_url":"https://github.com/marcelorodrigo/agent-skills/blob/HEAD/skills/filament-pro/SKILL.md","repo_owner":"marcelorodrigo","body_sha256":"ce55e10d2680bae8a0021d03478c9374181cc639dc3e675a345e0b296b4d13d0","cluster_key":"468aed8b2dbc47a1296f208bd450da5404dbd63e4e5369433ed9d0455b3718b9","clean_bundle":{"format":"clean-skill-bundle-v1","source":"marcelorodrigo/agent-skills/skills/filament-pro/SKILL.md","attachments":[{"id":"4b871470-29fb-57ca-8257-93183788d688","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4b871470-29fb-57ca-8257-93183788d688/attachment.md","path":"references/actions.md","size":17192,"sha256":"c85995c122f2bbcf4d6a7b9d2e763d31cc62e6f096c6e15bcc4b07abcf47e308","contentType":"text/markdown; charset=utf-8"},{"id":"59701b9a-c7d7-54bd-a617-f25c445585e1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/59701b9a-c7d7-54bd-a617-f25c445585e1/attachment.md","path":"references/authorization.md","size":18680,"sha256":"958c608a117d7fc2af9f970b0f05b9fa05d7cb724ddcd0700329e311bf4dfa58","contentType":"text/markdown; charset=utf-8"},{"id":"3c48f7df-8e6b-54fa-9007-18d711750446","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3c48f7df-8e6b-54fa-9007-18d711750446/attachment.md","path":"references/examples.md","size":25171,"sha256":"81811b4e97ea0c9c25c454534ba645b3ab9f8e36e3330fe181d284a0dc1f8c69","contentType":"text/markdown; charset=utf-8"},{"id":"501ecf61-c0a3-52c8-827f-ab269e74df0f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/501ecf61-c0a3-52c8-827f-ab269e74df0f/attachment.md","path":"references/forms.md","size":17436,"sha256":"e470cbafb585e239d874247c3ef0b7a2484dcfd016b37df1af1af4be5f77bf46","contentType":"text/markdown; charset=utf-8"},{"id":"547cb267-0452-5ad2-8b36-1c9deb8c30a8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/547cb267-0452-5ad2-8b36-1c9deb8c30a8/attachment.md","path":"references/infolists.md","size":16062,"sha256":"1475878c5fccfa351793f7b2ef0ff54472270f3a1c3436e2c7cb47b008e6d6ee","contentType":"text/markdown; charset=utf-8"},{"id":"07c4e21d-a340-58e7-a2ce-f87c4cf6fbd2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/07c4e21d-a340-58e7-a2ce-f87c4cf6fbd2/attachment.md","path":"references/notifications.md","size":10053,"sha256":"9d1f570b780f52f82a178d2058eac7f982496d67d747fa2bcf26b9341c765dfc","contentType":"text/markdown; charset=utf-8"},{"id":"9e634181-b447-5f3c-b2fc-54f2b228fadd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9e634181-b447-5f3c-b2fc-54f2b228fadd/attachment.md","path":"references/resources.md","size":24527,"sha256":"a2a9dae7c5cfc14f3f872f12c233ad1231e4ca0e01d07d61407b556803bfbf03","contentType":"text/markdown; charset=utf-8"},{"id":"b303a59a-c07e-52ac-91ed-91e2d8f977b7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b303a59a-c07e-52ac-91ed-91e2d8f977b7/attachment.md","path":"references/schemas.md","size":14038,"sha256":"ebbe80586838e4e084839b608ef7cad9d99cd26d4e29f3b9b7d54f7be8a825f9","contentType":"text/markdown; charset=utf-8"},{"id":"fec0578d-8d09-5fb4-9679-e1b729648a4b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fec0578d-8d09-5fb4-9679-e1b729648a4b/attachment.md","path":"references/tables.md","size":20029,"sha256":"e902e9aba3ce8ab87808bdb842bf32e55a005ed748847a6ae2d5ece3b0f88884","contentType":"text/markdown; charset=utf-8"},{"id":"64a6d409-93ec-5fa5-b098-400365ceb1f1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/64a6d409-93ec-5fa5-b098-400365ceb1f1/attachment.md","path":"references/testing.md","size":22320,"sha256":"caa5163f927743b92f962111dd6abb596a77564e74913c5c251e8dace18aaec8","contentType":"text/markdown; charset=utf-8"},{"id":"d8d105d6-fec8-5014-8597-80d90c265f95","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d8d105d6-fec8-5014-8597-80d90c265f95/attachment.md","path":"references/widgets.md","size":19435,"sha256":"d1972a73a72eff0203092e843284a3c328bbb96ef558655f81c8f10f08b7ee4a","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"e5f1d7889a6b0f043521de0b419987989578f38c2467a3cc2f1b2715efafdc93","attachment_count":11,"text_attachments":11,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/filament-pro/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"testing-qa","metadata":{"version":"1.0.0"},"import_tag":"clean-skills-v1","description":"Build Laravel admin panels with Filament v5. Use for creating resources, forms, tables, widgets, and testing admin interfaces with Livewire v4.","compatibility":"Requires Laravel 11.28+, PHP 8.2+, Livewire v4, TailwindCSS v4.1+"}},"renderedAt":1782981099904}

Filament v5 Build powerful Laravel admin panels using Filament v5's server-driven UI with Schemas and Livewire v4 reactivity. Overview Filament v5 is a Laravel admin panel framework that provides complete CRUD interfaces, forms, tables, and dashboard components through a declarative PHP API. Built on Livewire v4, it offers real-time reactivity without writing JavaScript. Key Concepts - PanelProvider : Central configuration class defining your admin panel - Resources : Automatic CRUD interfaces for Eloquent models - Schemas : Declarative UI components (forms, tables, infolists) - Actions : Int…