Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Laravel Filament 3 Custom Resource Guide

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 --generate

The --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.
Filament Resource Class Structureform() MethodSchema ComponentsValidation RulesCreate / Edit UItable() MethodColumns & FiltersBulk ActionsList View RenderinggetRelations()RelationManagersNested CRUDTabbed InterfaceRenders Create/Edit PagesHandles Form SubmissionRenders List PageQueries DatabaseRenders Tabs/SectionsManages Child RecordsEloquent Model + Database
Laravel Filament 3 Custom Resource Guide: Three core methods map directly to UI components and database operations

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 title

This 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.

Choosing Relationship Strategy in Filament 3What relationship type?BelongsTo (Select Parent)HasMany (Few Items)HasMany (Many/Complex)Select Component->relationship('client')Searchable, PreloadableBest for FK selectionRepeater ComponentInline schema definitionSaves with parent formBest for <10 simple itemsRelationManagerSeparate class + tabIndependent CRUD opsBest for large datasetsRule: If child records exceed 10 or need independent lifecycle → RelationManagerIf child records are always edited with parent → Repeater
Laravel Filament 3 Custom Resource Guide: Decision framework for selecting the appropriate relationship component

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 TypeUse CasePerformance ImpactAuthorization Check
Header ActionCreate new record, export all, global importsLow (runs once per page load)can('create') policy
Row ActionEdit, delete, approve, generate PDF for single recordMedium (evaluated per visible row)can('update', $record) policy
Bulk ActionDelete selected, change status, assign batchHigh (operates on collection)can('deleteAny') or custom
Table Filter ActionDynamic filtering based on user role or contextVariable (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.

Filament 3 Authorization LayersLayer 1: Resource Access (canAccess)Determines if user can see menu item or access any recordRuns once per request • Checked before query execution • Hides entire sectionExample: Only partners can access "Financial Reports" resourceLayer 2: Record Visibility (Policy + Tenancy)Filters which records appear in lists and can be viewed/editedScoped by tenant/company • Evaluated per record • Uses Laravel PoliciesExample: Associates see only cases assigned to their teamLayer 3: Field-Level Permissions (Schema Visibility)Controls which form fields and table columns render for current userEvaluated during component rendering • Supports disabled/read-only statesExample: Paralegals view case details but not billing rate fields
Laravel Filament 3 Custom Resource Guide: Defense-in-depth authorization prevents data leakage at every access layer

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:

  1. Add ->with() eager loading in the table query for every relationship referenced in columns.
  2. Replace TextColumn::make('relation.name') with a joined column or cached attribute if the relationship is expensive.
  3. Implement pagination with reasonable defaults (25–50 records). Avoid "show all" options on tables exceeding 1,000 records.
  4. 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.

Frequently Asked Questions

Filament 3 is a TALL-stack admin panel builder for Laravel 11 and 12. It generates secure CRUD interfaces, tables, and forms directly from Eloquent models, drastically reducing boilerplate for custom business resources compared to building Blade templates manually.

The package is open-source and free. Development costs depend on complexity; a standard custom resource typically takes 4-8 hours for an experienced developer, costing roughly NPR 20,000–40,000 (USD 150–300) in the Nepal market depending on relation handling and validation logic.

Filament 3 requires PHP 8.2 minimum. It runs perfectly on PHP 8.2, 8.3, and 8.4 with Laravel 11 or 12. You do not need to upgrade to 8.4 solely for Filament compatibility, though 8.3 is currently the most stable production choice for 2026 deployments.

Run php artisan make:filament-resource ResourceName to generate the resource class, list page, edit page, and create page. Define your form schema in the form() method and table columns in the table() method. Filament handles routing, authorization policies, and basic CRUD operations automatically based on these definitions.

Yes, override the getEloquentQuery() method on your ListRecords page class. This allows you to add global scopes, eager load relationships to prevent N+1 issues, or filter records by tenant or user role. In my experience, this is essential for performant tables on large datasets where default queries become slow without explicit optimization.

Use the Select component with multiple() enabled or the CheckboxList component for simple pivots. For pivot data, use Repeater with a relationship() configuration. Always ensure your Eloquent model defines the relationship correctly first. On legal-tech portals I have built, this pattern reliably manages document-category tagging and service-area assignments without custom controllers.

Yes, provided you implement Laravel Policies. Filament respects authorize(), viewAny(), update(), and delete() policy methods by default. Never rely solely on UI hiding; always enforce server-side authorization. For projects like Mijar Law Associates, I combine Filament's native policy integration with Spatie Laravel Permission to ensure strict role-based access control over sensitive case files.

Use Action::make('name') inside the table's actions() array. Define the action logic using ->action(fn ($record) => ...). You can add confirmation dialogs, modal forms, and authorization checks directly. This replaces the need for separate controller endpoints for tasks like approving orders or sending notifications, keeping related logic encapsulated within the resource definition itself.

This usually indicates a missing or misconfigured Policy. Generate one via php artisan make:policy ModelPolicy and register it in AuthServiceProvider or let Laravel auto-discover it. Ensure the authenticated user has the required permissions. During deployment, also verify that opcache is cleared; stale policy caches frequently cause phantom 403s in production environments after code updates.

Enable pagination, avoid loading unnecessary relations, and use database indexes on filtered/sorted columns. Replace text search with full-text indexes if using MySQL 8.0+. Use deferLoading() on heavy tabs. On e-commerce projects like Nepal Gift Card, I found that disabling global counts and using cursor pagination significantly reduced memory usage on inventory tables exceeding 50,000 SKUs.

Absolutely. Filament lives alongside standard Laravel routes and controllers. You can link to external routes from Filament navigation or redirect to Filament resources from traditional Blade pages. They share the same session, auth guard, and middleware stack. This hybrid approach works well when migrating legacy admin panels incrementally rather than rewriting everything at once.

Use FileUpload or SpatieMediaLibraryUpload components with disk('private') for sensitive documents. Configure validation rules for MIME types and size limits. Store files outside public/webroot when possible. For legal portals handling marriage certificates or court documents, I always enforce private storage with signed URLs for download, ensuring files are never publicly accessible even if filenames are guessed.

Use Filament's built-in testing helpers extending Laravel's HTTP tests. Test form submissions, table rendering, and action execution via assertFormSet(), assertTableColumnExists(), and callAction(). Focus on business logic and authorization rather than UI rendering. In practice, testing the underlying Eloquent model and Policy separately provides faster feedback than full browser tests for most resource behaviors.

Publish Filament's translation files via php artisan filament:translations and add ne locale entries. Override static getModelLabel() and getPluralModelLabel() in your resource class for dynamic labels. For bilingual sites serving Nepal audiences, I maintain separate translation keys for legal terms to ensure accurate Nepali terminology in admin interfaces while keeping English fallbacks for developer-facing strings.

Avoid Filament when the interface requires highly custom interactive workflows, real-time collaborative editing, or consumer-facing designs diverging significantly from admin patterns. Filament excels at structured data management but becomes restrictive for unique UX demands. If you find yourself fighting the framework's conventions extensively, a custom Vue or Livewire component with standard Laravel controllers may be more maintainable long-term.

Share this article

Quick Contact Options
Choose how you want to connect me: