devlogx/filament-fathom-dashboard-widget
A Filament widget that brings Fathom analytics into your admin dashboard, showing key traffic stats and site metrics at a glance. Drop it into any panel to give your team quick visibility into visits, pageviews, and referrers without leaving the app.
devlogx/filament-fathom-dashboard-widget is a Filament dashboard widget that pulls stats from the Fathom Analytics API and renders them alongside your other Filament widgets. 1.0.0 is the first tagged release — treat it as the API baseline, not as a delta from anything.
Minimum path to a working widget:
composer require devlogx/filament-fathom-dashboard-widget
php artisan vendor:publish and pick the package's entry if the tag differs):
php artisan vendor:publish --tag="filament-fathom-dashboard-widget-config"
FATHOM_API_KEY=xxxxxxxxxxxxxxxx
FATHOM_SITE_ID=123456789
php artisan config:clear
public function panel(Panel $panel): Panel
{
return $panel
->widgets([
\Devlogx\FilamentFathomDashboardWidget\Widgets\FathomDashboardWidget::class,
]);
}
/admin and look at the dashboard.Where to look first: the published config file. It tells you which env keys the package actually reads, the default cache TTL, and whether the API base URL is overridable — that one file answers 80% of the questions you'll have on day one.
First real use case: replace the vague "traffic is fine" feeling with a number on the dashboard your content team already looks at. A pageviews/visitors/uniques tile for the last 7 and 30 days covers most of what people want before they open Fathom itself.
Register per panel, not globally. Filament widgets are panel-scoped. If you run ->discoverWidgets(), verify the package widget is actually discovered — packages often ship widgets outside the panel's default discovery path, in which case explicit registration is required.
One widget instance per site. The most common real pattern for agencies: several sites, one dashboard. Subclass the widget and hard-code or inject the site ID:
class ClientAStatsWidget extends FathomDashboardWidget
{
protected static ?string $heading = 'Client A — last 30 days';
protected function getSiteId(): string
{
return config('services.fathom.sites.client_a');
}
}
Whether getSiteId() exists as an overridable method depends on the 1.0.0 internals — check the class before relying on it; setting the site via the config key per-widget or via a constructor property is the fallback.
Tenancy / multi-tenant panels. Resolve the site ID from Filament::getTenant() rather than config, so each tenant sees only their own traffic. Pair this with a canView() override so a tenant can't render a widget bound to another tenant's site ID.
Cache deliberately. The dashboard is the highest-traffic page in most admin panels and every widget render is an API call. Set the TTL in config to match your tolerance for staleness — 5–15 minutes is normal; 60 seconds will burn through Fathom's rate limit on a busy team. If the widget supports Filament's pollingInterval, use minutes, not seconds.
Pair with Filament's own stats. Keep the package widget for Fathom-specific numbers (pageviews, uniques, avg. duration, bounce rate) and use StatsOverviewWidget for anything you already have in the database. Mixing sources in one widget makes the "why don't these numbers match" conversation worse.
Test with Http::fake(). The widget is just an HTTP client under the hood. Fake the Fathom aggregation endpoint to assert your widget renders a given payload, and to assert it degrades gracefully on a 401/429:
Http::fake([
'api.usefathom.com/*' => Http::response(['data' => [/* ... */]]),
]);
Custom rendering. If the package ships publishable views (vendor:publish --tag=...-views), publish them rather than editing vendor files — you'll want the layout to match your panel's other widgets anyway.
try/catch, log the exception, and show an explicit "unable to load analytics" state. Do this during integration, not after the first incident..env on your machine or short-circuit the widget in local (return early from canView()), otherwise every dashboard load makes a live outbound call and slows down your test suite.php artisan config:cache in deploy scripts will freeze whatever env values existed at build time. If the widget shows stale or missing data immediately after a deploy, this is almost always why.APP_TIMEZONE may not agree. "Today" can differ by a day at the edges. Settle on one — usually set the Fathom site to UTC and label the widget period explicitly ("last 30 days", not "this month").https://fathom.yourdomain.com). Check whether 1.0.0's config exposes a base URL override; if not, that's a hard blocker and worth a PR upstream.composer.json constraints tell you which Filament versions are supported. If you're on v4 and the package only allows ^3.0, don't force it — widget APIs changed around lazy loading and the HasWidgets registration path.canView() to gate on a permission or role. Analytics data is business-sensitive; it shouldn't be visible to every user who can reach /admin.config('...fathom.api_key') is non-null → the site ID matches the Fathom URL → a manual curl against api.usefathom.com/v1/aggregations with the same key returns data → the widget's cache key has been cleared (php artisan cache:clear) → storage/logs/laravel.log for the caught exception.canView(), column selection), and the published Blade view for layout. If the site ID and time range are only settable via config in 1.0.0, subclassing plus a per-instance property is the cleanest way to get multiple sites on one dashboard without forking.How can I help you explore Laravel packages today?