Filament PHP v5 Coding Standards
Comprehensive standards for building production-grade Filament v5 admin panels. Filament v5 requires PHP 8.3+, Laravel 13+, Livewire 4+, Tailwind CSS 4.1+. Follow these rules exactly. For detailed code examples, see REFERENCE.md.
When to Apply
Apply to ALL Filament work: resources, forms, tables, infolists, actions, widgets, dashboards, panels, relation managers, imports/exports, custom pages, multi-tenancy, testing, and deployment.
1. Project Structure
Directory Layout
- Domain-grouped, plural-named:
Resources/Shop/,Resources/Blog/,Resources/HR/ - Resources in plural directories:
Resources/Shop/Products/ProductResource.php - Schemas extracted:
Resources/Shop/Products/Schemas/ProductSchema.php - Tables extracted:
Resources/Shop/Products/Tables/ProductsTable.php - Multiple panels:
Providers/Filament/AdminPanelProvider.php,AppPanelProvider.php
Resource Complexity Tiers
- Simple (ManageRecords) -- modal CRUD, single page, no relation managers
- Standard (List+Create+Edit) -- separate pages, basic CRUD
- Full (List+Create+Edit+View) -- sub-navigation, relation managers, widgets
Naming Rules
- Plural resource directories:
Products/, notProduct/ - File names match class names:
ProductResource.php - Slugs kebab-case:
order-items - Namespace matches directory path exactly
2. Resource Architecture
Delegation Pattern
Resources delegate to dedicated Schema and Table classes. Keep resource classes slim.
class ProductResource extends Resource
{
protected static ?string $model = Product::class;
protected static ?string $slug = 'products';
protected static ?string $recordTitleAttribute = 'name';
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedShoppingBag;
protected static ?string $navigationGroup = 'Shop';
protected static ?int $navigationSort = 1;
public static function form(Schema $schema): Schema
{
return ProductSchema::configure($schema);
}
public static function table(Table $table): Table
{
return ProductsTable::configure($table);
}
}
Key Rules
- Always set
$recordTitleAttributefor global search - Navigation icons: always
Heroicon::Outlined*(not Solid for nav) - Use
Heroiconenum, never string icon names - All actions import from
Filament\Actions\*namespace only - Table methods:
recordActions()notactions(),groupedBulkActions()notbulkActions()
3. Form Design
- Top-level:
$schema->components([...])withSectionwrapping - Section from
Filament\Schemas\Components\Section - Layout components (Section, Grid, Tabs, Flex) from
Filament\Schemas\Components\* - Slug fields:
->live(onBlur: true), create-only viaOperation::Create,->disabled()->dehydrated() - Selects with relationships: always
->searchable()and->preload() - File uploads: set
->disk(),->directory(),->visibility() - Use
Operationenum for conditional logic:Operation::Create,Operation::Edit - Repeaters for line items with calculated fields
- Tabs for complex multi-section forms
- All text labels via language files, never hardcoded
4. Table Design
- Columns: TextColumn, IconColumn (booleans), BadgeColumn (enums), ImageColumn
- Toggleable columns:
->toggleable(isToggledHiddenByDefault: true)for less important columns - Filters: SelectFilter, TernaryFilter (boolean), date range via custom filter
- Actions wrapped in
ActionGroup: View, Edit, Delete grouped - Bulk actions via
groupedBulkActions(): delete, export, status change - Toolbar actions via
toolbarActions(): create, import, export - Default sort:
->defaultSort('created_at', 'desc') - Searchable columns:
->searchable()on key text columns - Money:
->money('USD')or->formatStateUsing()for custom formatting
5. Infolist (View) Patterns
- Entry types: TextEntry, IconEntry (boolean), ImageEntry, BadgeEntry (enum)
- Inline labels for compact display:
->inlineLabel() - Rich content:
->prose()->markdown()for formatted text - Tabs for complex view pages
- Contextual actions on view pages with visibility conditions
- Key-value entries for metadata
6. Enum Design System
Every status/type/category uses a PHP 8.1 backed string enum implementing Filament contracts:
HasLabel-- display nameHasColor-- semantic colorHasIcon-- Heroicon
Semantic colors: success (positive), danger (negative), warning (caution), info (new), primary (special), gray (neutral)
Naming: PascalCase case names, snake_case backed values. Cast in model: 'status' => OrderStatus::class
7. Actions & Notifications
- All actions:
Filament\Actions\Action,Filament\Actions\CreateAction, etc. - Never import from
Filament\Tables\Actions\*(removed in v5) - Action modals use
->schema()not->form() - ActionGroup ordering: View, Edit, Delete (most common first)
- Notifications:
Notification::make()->title()->success()->send() - Refresh after action:
$this->dispatch('$refresh')or return redirect - Bulk actions:
->authorizeIndividualRecords()for policy-gated bulk operations
8. Relation Managers
- Standard: extend
RelationManager, defineform()andtable() - Alternative:
ManageRelatedRecordspage for complex relations - Action placement:
headerActions(create),recordActions(per row),groupedBulkActions(selected) - Reuse across resources when the same relation appears in multiple places
- Default sort on related records
9. Widgets & Dashboards
- StatsOverview: multiple stat cards, optional inline charts, descriptions, icons
- Chart widgets: line, bar, doughnut, pie -- extend
ChartWidget - Lazy loading: on by default (
$isLazy = true) - Polling: default 5s, customize via
$pollingIntervalornullto disable - Dashboard filters: implement
HasFiltersFormfor filterable dashboards - Resource page widgets: use
ExposesTableToWidgetstrait - Widget sort order via
$sortproperty
10. Multi-Tenancy
- Panel:
->tenant(Team::class)in PanelProvider - User model: implement
HasTenantswithgetTenants()andcanAccessTenant() - Automatic query scoping on all resources (opt-out with
$isScopedToTenant = false) - CRITICAL: Form selects are NOT auto-scoped -- add
modifyQueryUsingwithFilament::getTenant() - Validation: use
->scopedUnique()and->scopedExists()for tenant-aware rules - Domain-based:
->tenantDomain('{tenant:slug}.example.com') - Path-based with slug:
->tenant(Team::class, slugAttribute: 'slug') - Registration page: extend
RegisterTenant - Profile page: extend
EditTenantProfile
11. Multi-Panel Architecture
- Separate PanelProvider per panel (admin, app, vendor)
- Each panel: own auth, resources, pages, widgets, middleware, theme
FilamentUser::canAccessPanel()controls per-panel access- Shared config via Plugin class or static helper
Filament::setCurrentPanel('app')for testing non-default panels
12. Custom Pages & Clusters
- Custom pages:
php artisan make:filament-page Settings - Access control:
canAccess()method - Header actions, header/footer widgets, widget data passing
- Clusters: group related pages under sub-navigation
SubNavigationPosition::Topfor horizontal tabs- Resource sub-navigation:
getRecordSubNavigation()for View/Edit/Related pages
13. Testing (Pest + Livewire)
- Test via
livewire(PageClass::class)-- pages are Livewire components - Auth:
actingAs(User::factory()->create())inbeforeEach - Multi-tenant:
Filament::setTenant($team)+Filament::bootCurrentPanel() - Multi-panel:
Filament::setCurrentPanel('admin')
Key test patterns:
- List:
->assertCanSeeTableRecords(),->searchTable(),->sortTable(),->filterTable() - Create:
->fillForm([...])->call('create')->assertHasNoFormErrors()->assertNotified() - Edit: `->asser