Documentation Index
Fetch the complete documentation index at: https://mintlify.com/filamentphp/filament/llms.txt
Use this file to discover all available pages before exploring further.
Introduction
Filament applications can handle large datasets and complex UIs efficiently when properly optimized. This guide covers various techniques to improve performance at the database, application, and frontend levels.
Database optimization
Eager loading relationships
Prevent N+1 query problems by eager loading relationships in your resources:
use Illuminate\Database\Eloquent\Builder;
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->with(['author', 'category', 'tags']);
}
For nested relationships:
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->with([
'author.profile',
'comments.user',
'category.parent',
]);
}
Counting relationships efficiently
Use withCount() instead of loading full relationships when you only need counts:
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->withCount(['comments', 'likes', 'views']);
}
In your table:
TextColumn::make('comments_count')
->label('Comments')
->sortable()
Selective column loading
Load only the columns you need:
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->select([
'id',
'title',
'author_id',
'published_at',
'created_at',
])
->with('author:id,name');
}
Database indexing
Add indexes to frequently queried and sorted columns:
Schema::table('posts', function (Blueprint $table) {
$table->index('published_at');
$table->index('author_id');
$table->index(['category_id', 'published_at']);
$table->fullText(['title', 'content']);
});
Query optimization for filters
Optimize filter queries:
use Filament\Tables\Filters\SelectFilter;
SelectFilter::make('category')
->relationship('category', 'name')
->preload() // Loads options once instead of on-demand
->searchable()
->multiple()
->query(function (Builder $query, array $data): Builder {
return $query->when(
filled($data['values']),
fn (Builder $query) => $query->whereIn('category_id', $data['values'])
);
})
Deferring table loading
Defer table rendering to improve initial page load:
public function table(Table $table): Table
{
return $table
->deferLoading()
->columns([/* ... */]);
}
Use appropriate pagination settings:
public function table(Table $table): Table
{
return $table
->defaultPaginationPageOption(25)
->paginationPageOptions([10, 25, 50, 100])
->extremePaginationLinks() // Shows first and last page links
->columns([/* ... */]);
}
For very large datasets, use simple pagination:
public function table(Table $table): Table
{
return $table
->simplePagination()
->columns([/* ... */]);
}
Lazy loading columns
Lazy load expensive column calculations:
TextColumn::make('statistics')
->state(function (Model $record): string {
// This is only calculated when the column is visible
return cache()->remember(
"post.{$record->id}.statistics",
now()->addHours(1),
fn () => $this->calculateComplexStatistics($record)
);
})
Disable unnecessary features
Disable features you don’t need:
public function table(Table $table): Table
{
return $table
->selectCurrentPageOnly() // Only select records on current page
->searchOnBlur() // Search on blur instead of keystroke
->striped(false) // Disable striping for slight performance gain
->columns([/* ... */]);
}
Caching strategies
Caching select options
Cache expensive select option queries:
use Illuminate\Support\Facades\Cache;
Select::make('category_id')
->options(function (): array {
return Cache::remember(
'category-options',
now()->addHours(24),
fn () => Category::pluck('name', 'id')->toArray()
);
})
->searchable()
Cache widget data for dashboards:
protected function getStats(): array
{
return Cache::remember(
'dashboard-stats',
now()->addMinutes(5),
function (): array {
return [
Stat::make('Total Users', User::count())
->description('32k increase')
->descriptionIcon('heroicon-m-arrow-trending-up')
->color('success'),
Stat::make('Revenue', number_format(Order::sum('total') / 100, 2))
->description('7% increase')
->descriptionIcon('heroicon-m-arrow-trending-up')
->color('success'),
];
}
);
}
Invalidating caches
Invalidate caches when data changes:
protected function afterSave(): void
{
Cache::forget('category-options');
Cache::tags(['posts', "post.{$this->record->id}"])->flush();
}
Asset optimization
Preload select options to reduce queries:
Select::make('author_id')
->relationship('author', 'name')
->preload() // Loads all options immediately
->searchable()
Or use getOptionLabelFromRecordUsing() to avoid extra queries:
Select::make('author_id')
->relationship(
name: 'author',
titleAttribute: 'name',
modifyQueryUsing: fn (Builder $query) => $query->select(['id', 'name'])
)
->searchable()
->preload()
Optimizing file uploads
Optimize image uploads with automatic resizing:
FileUpload::make('featured_image')
->image()
->imageResizeMode('cover')
->imageCropAspectRatio('16:9')
->imageResizeTargetWidth(1920)
->imageResizeTargetHeight(1080)
->imagePreviewHeight(250)
->optimize('webp') // Convert to WebP format
->maxSize(2048) // 2MB limit
Livewire optimization
Reducing Livewire updates
Use lazy() for heavy calculations:
TextInput::make('title')
->lazy() // Only updates on blur, not every keystroke
->debounce(500) // Wait 500ms after typing stops
Polling optimization
Optimize polling intervals:
protected function getHeaderWidgets(): array
{
return [
RealtimeStatsWidget::class,
];
}
// In the widget:
protected static ?string $pollingInterval = '30s'; // Poll every 30 seconds instead of default 2s
Offline support
Handle offline states gracefully:
public function table(Table $table): Table
{
return $table
->poll('30s')
->columns([/* ... */]);
}
Query optimization
Using chunking for bulk operations
Process large datasets in chunks:
use Illuminate\Support\Facades\DB;
BulkAction::make('process')
->action(function (Collection $records): void {
DB::transaction(function () use ($records) {
$records->chunk(100)->each(function (Collection $chunk): void {
// Process 100 records at a time
$chunk->each->process();
});
});
})
Lazy collections for memory efficiency
Use lazy collections for large datasets:
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\LazyCollection;
Action::make('export')
->action(function (): void {
Post::query()
->cursor() // Returns a lazy collection
->each(function (Post $post): void {
// Process one record at a time, memory-efficient
$this->exportRecord($post);
});
})
Optimizing search queries
Use full-text search for better performance:
protected function applySearchToTableQuery(Builder $query): Builder
{
$search = $this->tableSearch;
if (filled($search)) {
$query->whereFullText(['title', 'content'], $search);
}
return $query;
}
Advanced techniques
Using queue workers
Offload heavy operations to queues:
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
Action::make('process')
->action(function (Model $record): void {
ProcessPostJob::dispatch($record);
Notification::make()
->title('Processing started')
->success()
->send();
})
Database connection optimization
Use read/write connections:
// config/database.php
'mysql' => [
'read' => [
'host' => ['192.168.1.1'],
],
'write' => [
'host' => ['192.168.1.2'],
],
'sticky' => true,
// ... other config
],
Horizon for queue monitoring
Monitor queue performance with Laravel Horizon:
composer require laravel/horizon
php artisan horizon:install
php artisan horizon
Using Redis for caching
Configure Redis for better cache performance:
// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
],
Using Laravel Telescope
Monitor application performance:
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
Query logging
Log slow queries for optimization:
// In a service provider
use Illuminate\Support\Facades\DB;
DB::listen(function ($query) {
if ($query->time > 1000) { // Queries taking more than 1 second
Log::warning('Slow query detected', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time' => $query->time,
]);
}
});
Use Clockwork for profiling:
composer require itsgoingd/clockwork --dev
Best practices
- Always eager load relationships to prevent N+1 queries
- Use database indexing on frequently queried columns
- Cache expensive operations and calculations
- Use pagination for large datasets
- Implement lazy loading for heavy columns
- Use
withCount() instead of loading full relationships
- Optimize file uploads with compression and resizing
- Use queues for long-running operations
- Monitor slow queries and optimize them
- Use Redis for caching in production
- Implement full-text search for large text searches
- Use
select() to load only needed columns
- Process bulk operations in chunks
- Use lazy collections for memory efficiency
- Defer table loading when appropriate
- Minimize Livewire polling intervals
- Use database transactions for consistency
- Profile your application regularly