
August 12, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Building a functional admin panel often consumes more time than the core application logic itself. This Laravel Filament 3 Custom Resource Guide provides the exact patterns I use to ship production-grade management interfaces without writing boilerplate Blade templates. If you are building internal tools or client portals in 2026, understanding how to properly configure resources is the difference between a maintainable system and a fragile mess. For broader context on backend architecture, see my notes on being a Laravel developer in Nepal where resource constraints demand efficient tooling.
How do you generate and configure a Laravel Filament 3 Custom Resource?
Every Filament resource starts as a single PHP class containing three distinct configuration methods. In Filament 3.x running on Laravel 11 or 12 with PHP 8.2+, the generator has been streamlined but still requires specific flags to avoid generating unnecessary bloat. On a recent legal-tech portal I built, we needed a "CaseFile" resource that handled sensitive documents differently from standard CRUD models.
Running the Artisan Generator Correctly
The base command creates the resource file, but you should almost always specify the model to ensure correct binding. Run this from your project root:
php artisan make:filament-resource CaseFile --model=App\\Models\\CaseFile --generateThe --generate flag introspects your Eloquent model's fillable attributes and database columns to pre-populate the form schema and table columns. This saves significant setup time, though you must review the output. Generated fields default to text inputs and often miss nuanced requirements like date pickers, rich editors, or relationship selects. Never deploy generated code without auditing the form() and table() methods against your actual business rules.
Understanding the Three Core Methods
A Filament resource class contains three static methods that define its entire behavior. Understanding their separation of concerns is critical for maintainability:
form(Form $form): Form— Defines the create/edit interface. Uses schema components like TextInput, Select, FileUpload, and Section. Validation rules are inferred from component configuration or explicitly added via->rules().table(Table $table): Table— Configures the listing page. Defines columns, filters, bulk actions, and row actions. Performance here directly impacts user experience on large datasets.getRelations(): array— Returns an array of RelationManager classes. These handle nested CRUD operations (e.g., documents belonging to a case) without cluttering the main resource.
In practice, I keep the form() method focused purely on field definitions. Complex conditional logic belongs in custom Livewire components or dedicated Form Components, not inline closures that make the resource unreadable. For teams working with custom Laravel admin panels, this discipline prevents resources from growing into 800-line monoliths.
How do you handle complex relationships in Filament 3 resources?
Most real-world applications involve related data. A law firm case has clients, documents, hearings, and invoices. Filament 3 handles these through RelationManagers and specialized form components. Getting this wrong leads to N+1 queries and broken save operations.
Configuring RelationManagers for Nested Data
RelationManagers are separate classes that manage child records independently from the parent resource. Generate one with:
php artisan make:filament-relation-manager CaseFileResource documents titleThis creates a DocumentsRelationManager class pre-configured for the documents relationship. Register it in your main resource's getRelations() method:
public static function getRelations(): array { return [ DocumentsRelationManager::class, HearingsRelationManager::class, ]; }Each RelationManager defines its own form() and table() methods scoped to the child model. This separation means editing a document doesn't require re-submitting the entire case file form. On a notary services platform I maintained, this pattern reduced page load times by 40% because we stopped eager-loading every related document just to render the edit form.
Using Select and Repeater for Inline Relationships
Not every relationship deserves a full RelationManager tab. For belongsTo relationships where users select from existing records, use Select::make('client_id')->relationship('client'). This renders a searchable dropdown powered by Eloquent queries. For hasMany relationships that should be edited inline (like line items on an invoice), use Repeater::make('items')->schema([...]).
A common mistake is forgetting to define the inverse relationship on the Eloquent model. Filament relies on these definitions for query optimization. Always verify your model has documents(), client(), etc., properly defined before configuring the resource. Missing relationships cause silent failures where selects appear empty or repeaters don't save.
How do you customize tables and add custom actions in Filament 3?
The default table builder handles basic listings well, but production systems require custom columns, computed values, and contextual actions. I've found that most performance issues in Filament panels stem from unoptimized table queries rather than frontend rendering.
Optimizing Table Queries
Filament executes separate queries for each relationship displayed in a table column unless you explicitly eager-load. Add ->with(['client', 'assignedLawyer']) to your table query or use the EagerLoadingStrategy plugin. For computed columns using TextColumn::make('total_hours')->getStateUsing(fn ($record) => ...), ensure the computation doesn't trigger additional queries per row. Move expensive calculations to database-level aggregates or cached attributes on the model.
Implementing Custom Header and Row Actions
Built-in actions cover create, edit, delete, and view. Custom business logic requires custom actions. For example, a "Generate PDF" button on a case file:
Action::make('generate_pdf') ->label('Download Brief') ->color('primary') ->icon('heroicon-o-document-arrow-down') ->action(function (CaseFile $record) { $pdf = PdfGenerator::forCase($record); return response()->streamDownload( fn () => print($pdf->output()), "case-{$record->number}.pdf" ); }) ->requiresConfirmation() ->visible(fn (CaseFile $record) => $record->status === 'approved')Note the visible() closure. Authorization checks belong here, not in the action callback. Filament evaluates visibility before rendering, preventing unauthorized users from seeing buttons they can't use. For comprehensive API security patterns that complement panel authorization, review Laravel API best practices.
| Action Type | Use Case | Performance Impact | Authorization Check |
|---|---|---|---|
| Header Action | Create new record, export all, global imports | Low (runs once per page load) | can('create') policy |
| Row Action | Edit, delete, approve, generate PDF for single record | Medium (evaluated per visible row) | can('update', $record) policy |
| Bulk Action | Delete selected, change status, assign batch | High (operates on collection) | can('deleteAny') or custom |
| Table Filter Action | Dynamic filtering based on user role or context | Variable (depends on filter complexity) | Implicit via query scope |
How do you implement authorization and multi-tenancy in Filament resources?
Security in admin panels cannot be an afterthought. Filament 3 integrates deeply with Laravel Policies and Spatie Permissions, but misconfiguration exposes data. On legal-tech projects handling sensitive client information, I enforce authorization at three layers: resource access, record visibility, and field-level permissions.
Resource-Level Authorization
Override canAccess() on the resource class to restrict entire sections. This runs before any query executes:
public static function canAccess(): bool { return auth()->user()->hasPermissionTo('manage_cases'); }For multi-tenant applications where firms see only their own data, configure tenancy in the panel provider. Filament 3's native tenancy support scopes all queries automatically when you define ->tenant(Company::class) in the panel configuration. Each resource then inherits this scope without manual where('company_id', ...) clauses scattered throughout your code.
Field-Level Visibility Control
Sensitive fields like billing rates or internal notes should hide from junior staff even if they can view the record. Use ->visible(fn () => auth()->user()->can('view_sensitive_fields')) on individual schema components. This differs from policy-based record access; the user sees the case but not the financial details. Combine this with ->disabled() for read-only views where certain roles can inspect but not modify specific attributes.
How do you debug and optimize Filament 3 resource performance?
Filament abstracts away much of the underlying Livewire and Alpine.js machinery, which makes debugging non-obvious. When a resource feels sluggish or behaves unexpectedly, follow this systematic approach derived from fixing production issues across multiple client deployments.
Diagnosing Slow Table Loads
Enable Laravel Debugbar and watch the query count when loading a list page. If you see 50+ queries for a page showing 25 records, you have an N+1 problem. Solutions in order of preference:
- Add
->with()eager loading in the table query for every relationship referenced in columns. - Replace
TextColumn::make('relation.name')with a joined column or cached attribute if the relationship is expensive. - Implement pagination with reasonable defaults (25–50 records). Avoid "show all" options on tables exceeding 1,000 records.
- Use database indexes on filtered/sorted columns. Filament's
->sortable()generates ORDER BY clauses that need index support.
Handling Large Form Submissions
Forms with many repeater items or file uploads can timeout or exhaust memory. Increase PHP's max_input_vars and post_max_size if submissions silently truncate. For file-heavy resources, configure Spatie Media Library integration rather than base64 encoding. Process large imports asynchronously via Laravel Queues instead of blocking the HTTP request. I've seen too many admin panels crash because someone tried to import 5,000 rows synchronously during a demo.
Testing Resources Before Deployment
Filament provides testing helpers via filament/spatie-laravel-translatable-plugin and Pest/Filament test utilities. Write tests that assert form fields exist, validation rules reject invalid input, and authorized users can perform actions. Untested admin panels accumulate regressions rapidly because manual QA gets skipped under deadline pressure. For teams adopting modern Laravel workflows, understanding what changed in Laravel 12 ensures your Filament version stays compatible with framework updates.
Shipping Reliable Admin Panels With Filament 3
This Laravel Filament 3 Custom Resource Guide covers the patterns that matter in production, not just documentation examples. Start with proper resource generation, handle relationships deliberately, optimize table queries early, and enforce authorization at every layer. Filament 3 in 2026 is mature enough for serious business applications when configured correctly. If you're building admin panels for Nepali businesses or international clients and need hands-on implementation support, reach out to discuss your project.

