charlielangridge/fathom-stats-display
Display Fathom Analytics stats directly in your Laravel admin panel. This lightweight package pulls page views, unique visitors, and traffic insights from Fathom's privacy-friendly API, giving you quick analytics visibility without leaving your app.
This is a small, single-maintainer package (3 stars, no dependents) that surfaces Fathom Analytics stats inside a Laravel app. Because the surface area is tiny and the documentation is thin, treat the source as the documentation.
Install and inspect before you configure.
composer require charlielangridge/fathom-stats-display
Then open vendor/charlielangridge/fathom-stats-display/ and read composer.json (Laravel/PHP version constraints), the service provider, and any config/ or resources/views/ directories. That is 10 minutes of work that will save you an hour of guessing.
Publish the config and views. Packages of this shape almost always ship a config file and Blade views:
php artisan vendor:publish --provider="CharlieLangridge\FathomStatsDisplay\FathomStatsDisplayServiceProvider"
If the provider name differs, run php artisan vendor:publish and pick the tagged entry interactively rather than guessing the FQCN.
Set credentials in .env. You will need a Fathom API token (Fathom account settings → API) and the numeric site ID:
FATHOM_API_TOKEN=xxxxxxxxxxxxxxxxxxxx
FATHOM_SITE_ID=123456789
Confirm the exact key names against the published config — do not assume.
First use case: an internal stats page. The highest-value first integration is a single authenticated route rendering the package's default view. Wire it behind auth (and probably an admin gate) immediately:
Route::middleware(['auth', 'can:view-analytics'])->group(function () {
Route::get('/admin/analytics', fn () => view('analytics.dashboard'));
});
In analytics/dashboard.blade.php, include the package component/view. Verify the token is not present in the rendered HTML before you ship it.
Wrap the package in your own view, not the other way around. Don't edit published package views in place; create resources/views/admin/analytics.blade.php that composes your layout and <x-fathom-stats ... /> (or @include the package view). This keeps upgrades cheap and lets you control the chrome around the stats.
Cache aggressively at the application layer. Fathom's API is rate-limited and you do not want a dashboard refresh to hit it per page load. Put a thin service in front of the package:
$stats = Cache::remember("fathom.stats.{$siteId}.{$range}", now()->addMinutes(30), function () use ($siteId, $range) {
return app(FathomStats::class)->forSite($siteId)->range($range)->get();
});
If the package exposes its own caching config, use it — but keep a Cache::remember wrapper anyway so you control TTL and cache keys.
Multi-site support via config, not code. If you run several properties, keep a map in config/fathom.php ('sites' => ['marketing' => env('FATHOM_SITE_MARKETING'), ...]) and resolve site IDs by key. This avoids sprinkling env calls through views and makes tests trivial with config()->set().
Scheduled snapshots for anything you want to trend. Dashboards that query live are fine for today; anything historical should be an Artisan command that pulls and persists into a local analytics_snapshots table, scheduled in routes/console.php or Kernel::schedule(). That decouples your reporting from Fathom's API availability and from the package's continued existence.
Livewire / Inertia integration. For Livewire, bind the stats fetch to a computed property and let the component refresh; for Inertia, resolve the stats in the controller and pass as props so the API call never leaks to the client. Avoid calling the Fathom API from the browser — the token is a server-side secret.
Testing. Bind a fake implementation of whatever the package's client class is ($this->instance(...) or $this->mock(...)) so your feature tests don't hit the network. If the package isn't designed for that, isolate it behind your own AnalyticsRepository interface and fake that.
illuminate/support tightly. Run composer why-not if install fails and consider --with-all-dependencies only after reading what it would change.->range(Carbon::today()->subDays(29), Carbon::today())) and don't assume the package does it for you.composer update won't bring view changes. Keep a note in your repo of what you changed, or override via your own partials instead.GuzzleHttp + /api/v1/aggregations) if the package becomes a liability.APP_DEBUG=true locally and check storage/logs/laravel.log — most packages of this size log failures quietly and render an empty component.How can I help you explore Laravel packages today?