Table resources — the biggest performance trap
A Filament Resource renders a Livewire-powered table. Each column is a template expression; relationship columns lazy-load their relations on access:
Bad — fires N queries per page load
class UserResource extends Resource
{
public static function table(Table $table): Table
{
return $table->columns([
TextColumn::make('name'),
TextColumn::make('latestOrder.total'), // N+1
TextColumn::make('company.name'), // N+1
TextColumn::make('subscription.plan'), // N+1
]);
}
}Good — eager-load in getTableQuery
class UserResource extends Resource
{
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->with(['latestOrder', 'company', 'subscription']);
}
}
For complex tables with counts, use withCount() instead of computing in the view. Aggregate columns should be SQL-side whenever possible.
Widget caching
Filament widgets re-render on page load and on ->poll() interval. Uncached queries run every time:
class TotalRevenueWidget extends StatsOverviewWidget
{
protected static ?string $pollingInterval = '30s';
protected function getStats(): array
{
// Bad — hits DB every 30s
// $total = Order::where('paid', true)->sum('total');
// Good — cached
$total = Cache::remember('stats.total_revenue', 30, fn () =>
Order::where('paid', true)->sum('total')
);
return [
Stat::make('Revenue', '$' . number_format($total, 2)),
];
}
}Admin auth failure tracking
/admin is a high-value target for attackers. Track failed logins:
app/Providers/AppServiceProvider.php
use Illuminate\Auth\Events\Failed;
use Illuminate\Support\Facades\Event;
Event::listen(function (Failed $event) {
if (str_contains(request()->path(), 'admin')) {
logger()->warning('Admin login failure', [
'email' => $event->credentials['email'] ?? null,
'ip' => request()->ip(),
'ua' => request()->userAgent(),
]);
}
});Alert on failed-login rate spikes per IP. Combine with Laravel's built-in throttling and ideally IP-allowlisting at the load balancer.
What to track per resource
- List page load — should be < 400ms p95 for admin usability
- Row actions — edit / delete / bulk. Target < 300ms per Livewire update.
- Form submit — complex forms with many fields can be slow on save. Profile the Resource model's save() path.
- Bulk actions — users expect these to be reasonably fast. If bulk is slow (> 2s for 50 rows), queue them instead.
Filtering your APM to admin traffic
In most APMs, filter requests by URL prefix:
- /admin/* — panel page loads, form submits
- /livewire/update — table filters, actions, form field updates
- /admin/login — auth attempts
THE EASY WAY
Catch Filament bottlenecks with per-request tracing
NightOwl records every /admin request with full trace breakdown — see which Livewire update fired 340 DB queries, which table resource loaded 50 MB of eager-loaded relations. The request drilldown turns Filament slowness from guesswork into a one-line fix.
composer require nightowl/agent
php artisan nightowl:install