dotswan/filament-laravel-pulse
Filamental Laravel Pulse integrates Laravel Pulse monitoring into your Filament admin panel, providing a ready-made dashboard to view application performance, slow requests, queues, cache, and usage metrics without leaving Filament.
Install and verify Pulse first. This package is a thin Filament presentation layer over laravel/pulse; almost every failure people hit is actually a Pulse install or ingestion problem, not a widget problem.
composer require laravel/pulse
php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider"
php artisan migrate # creates pulse_entries, pulse_values, pulse_aggregates, pulse_*_keys
composer require dotswan/filament-laravel-pulse
Register the plugin on the panel you actually use (usually app/Providers/Filament/AdminPanelProvider.php):
use Dotswan\FilamentLaravelPulse\FilamentLaravelPulsePlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugin(FilamentLaravelPulsePlugin::make());
}
Confirm data is flowing before touching the UI. Open /pulse (the native Pulse dashboard) and check that requests/servers are being recorded. If that page is empty, the Filament widgets will be empty too. For local work:
php artisan pulse:check # server metrics, run as a long-lived process
php artisan pulse:work # queue ingestion worker
Where to look first:
config/pulse.php — enabled, storage, ingest, recording settings decide whether anything reaches the tables.config/filament-laravel-pulse.php (publishable) — widget-level defaults, thresholds, cache TTLs.getData() to understand exactly which pulse_* table and time window it queries.First real-world use case: add PulseUsage + PulseServers to the main dashboard so you can see throughput and app-server health alongside business metrics, then move the heavier widgets (slow queries, exceptions, slow jobs) onto a dedicated "Monitoring" page so you don't slow down the page your team opens all day.
The plugin registers widgets globally for the panel. That's fine for a small set, but most teams end up with a dedicated page and register widgets there instead, so widgets are lazy-loaded only when someone opens monitoring:
// app/Filament/Pages/Monitoring.php
use Dotswan\FilamentLaravelPulse\Widgets\{PulseUsage, PulseServers, PulseSlowQueries, PulseExceptions};
protected static string $view = 'filament.pages.monitoring';
protected static ?string $navigationIcon = 'heroicon-o-chart-bar';
protected function getHeaderWidgets(): array
{
return [
PulseUsage::class,
PulseServers::class,
];
}
protected function getFooterWidgets(): array
{
return [
PulseSlowQueries::class,
PulseExceptions::class,
];
}
public function getHeaderWidgetsColumns(): int|array
{
return 2;
}
Trim the global registration down to what's cheap (usage/servers) and keep the expensive ones on the page. Filament lazy-loads each widget via a separate Livewire request, so a page with nine widgets fires nine requests — a broken widget shows as a permanent "Loading…" card and nothing in the page error log.
Common pattern: separate "Overview", "Performance", and "Reliability" pages, each with three or four widgets. Restrict via canAccess() so the reliability page (exceptions, slow jobs) is visible to devs/on-call only:
public static function canAccess(): bool
{
return auth()->user()?->hasAnyRole(['admin', 'on-call']);
}
Pulse widgets are Filament widgets, so the usual column API applies. Wide cards ($columnSpan = 'full') for graphs, 1 or 2 for stat cards. If you're mixing with non-Pulse widgets on the same dashboard, put the Pulse ones in a separate header section — the default card heights don't always match and you get ragged rows.
Running both is normal and recommended during rollout: native /pulse for deep-dives and debugging, Filament widgets for the at-a-glance view. Don't let the Filament plugin become the only way to reach Pulse — when a widget misbehaves, the native page tells you whether it's a presentation bug or a data bug in seconds.
Rather than editing vendor code, extend a widget and override the data method or the threshold properties. This is the cleanest extension point for things like "only show slow queries over 500ms" or "group by route prefix":
class OurSlowQueries extends PulseSlowQueries
{
protected function getData(): array
{
$data = parent::getData();
// filter / relabel here
return $data;
}
}
Then reference OurSlowQueries::class in the page. Same approach for changing chart colors to match your panel theme.
Monitoring widgets are only as good as the ingestion setup behind them:
pulse:check on one node (or a scheduled command), not all web nodes.pulse:work under Supervisor, separate from your normal queue workers, so a stuck app queue doesn't blind you.local. Set PULSE_ENABLED=true in staging/production or your widgets will render empty forever and look broken.pulse_* tables — make sure pulse:work is actually draining Redis into them.pulse:clear per type, or configure the trim) — otherwise pulse_entries grows unbounded and the slow-query/slow-request widgets become the slowest thing in your app.Empty widgets almost always mean one of four things: PULSE_ENABLED is false, the Pulse migrations were never run, pulse:check/pulse:work aren't running, or your cache store is misconfigured so Pulse cards can't persist. Check /pulse first; it eliminates the widget layer immediately.
Missing pulse_* tables throw SQL errors inside a lazy-loaded request. The page renders fine, the widget spins, and the actual exception is buried in storage/logs/laravel.log or the livewire request in Telescope. If a single card is stuck, hit the Livewire update endpoint directly or check the log — don't assume it's a CSS issue.
Widgets show aggregated, slightly stale data. Pulse cards are cached (default ~15s, configurable via PULSE_CACHE and the package config). During an incident, don't debug the widget — check the raw tables. If numbers look frozen, clear the cache before assuming ingestion broke.
Auth is not inherited from Pulse's gate. Native Pulse protects /pulse with the viewPulse gate. These widgets render inside your panel and rely on Filament's panel access / canView(). If you assumed the gate covered the widgets, you may have just exposed server names and exception traces to every panel user. Add canView()/canAccess() explicitly.
Cost scales with table size. Slow-queries, slow-requests, slow-jobs, and exceptions scan pulse_entries/pulse_aggregates over a time window. On a busy app after a few weeks, a dashboard with all of them becomes noticeably slow. Mitigations: keep only usage/servers on the default dashboard, aggressive trimming, and don't put Pulse widgets on a page that polls.
Widget polling compounds the problem. If you set a polling interval on a page with several Pulse widgets, you're re-running those aggregate queries every N seconds for every open tab. For monitoring, prefer manual refresh or a long interval (60s+).
Tenancy isn't modeled. Pulse records by key/type with no tenant column. In a multi-tenant panel, a tenant-scoped Pulse page either shows global data (likely a data-leak concern for exception messages) or needs custom filtering on pulse_entries. Decide deliberately rather than inheriting whatever the default widget shows.
Filament major-version compatibility. Check the package's composer.json constraints against your Filament version before upgrading the panel — this ecosystem is small and lags major Filament releases. Pin the plugin version in composer.json if you upgrade Filament incrementally.
Dark mode and theme drift. Chart colors and card backgrounds in Pulse-derived widgets don't always pick up a custom Filament theme. If widgets look out of place, override the chart color properties in a subclass rather than patching vendor views.
Publish the config once, then stop. php artisan vendor:publish --tag=filament-laravel-pulse-config gives you thresholds and cache settings. Keep the published file minimal and comment why each value differs from default — future you will not remember whether a threshold was tuned or accidental.
Testing. Don't unit-test the widgets against real Pulse tables. Seed a handful of rows into the relevant pulse_* table in a test case (or use Pulse's own fakes where available) and assert the widget renders; anything more couples your tests to Pulse internals.
Debugging checklist when a card is wrong, in order: native /pulse correct? → raw table has recent rows? → cache cleared? → log shows an exception on the lazy-load request? → config threshold changed? That order resolves roughly everything.
How can I help you explore Laravel packages today?