Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Fathom Laravel Package

marcreichel/laravel-fathom

A Laravel package for adding privacy-friendly Fathom Analytics to your app. Easily inject the tracking script via a Blade component and track events without compromising user privacy.

Deep Wiki
Context7

Getting Started

The package does two distinct things, and it helps to treat them as separate integrations from day one:

  1. A Blade component that emits the Fathom <script> tag — the "tracking" side.
  2. A thin client for the Fathom REST API — the "reporting" side.

Minimal setup:

composer require marcreichel/laravel-fathom
php artisan vendor:publish --tag="fathom-config"

Auto-discovery registers the service provider; the published config/fathom.php is where everything lives. At minimum you need:

FATHOM_SITE_ID=ABCDEFGH
FATHOM_API_TOKEN=your-api-token

FATHOM_SITE_ID comes from the Fathom dashboard (Site Settings → Site ID) and is public — it ships in your HTML anyway. FATHOM_API_TOKEN is a personal API token from your Fathom account settings and is secret: never expose it client-side, never commit it, and keep it out of any config you ship to a browser bundle.

Drop the script into your layout:

{{-- resources/views/layouts/app.blade.php --}}
<head>
    ...
    <x-fathom::script />
</head>

First use case: confirm the beacon fires. Load the page, open DevTools → Network, filter for fathom, and check the script.js request plus the subsequent pageview request to the tracking endpoint. If the script loads but no pageview appears, the site ID is wrong or an ad-blocker is swallowing it — both are common and both look identical in the Fathom dashboard (silence).

The second thing to wire up immediately is the API side, even if you don't use it yet: run a throwaway php artisan tinker call against the API to confirm the token works and to see the response shape before you build a dashboard on assumptions.

Implementation Patterns

Environment guard in the layout. The single most useful pattern is not rendering the tag outside production. Either wrap the component or lean on an enabled flag in config:

@production
    <x-fathom::script />
@endproduction

This keeps local and staging traffic out of your real analytics without relying on Fathom's domain filtering.

Consent-gated loading. For GDPR-sensitive sites, don't render the component server-side until consent exists. Because the component is just Blade, you can defer it behind a cookie check:

@if (request()->cookie('analytics_consent') === 'granted')
    <x-fathom::script />
@endif

If consent is granted client-side via a banner, render the component into a <template> and inject it from JS, or publish the package views and add a data-* hook so your banner can load the script lazily.

SPA / Inertia / Livewire. A plain <script> tag fires once on initial load. In an Inertia or Turbo app, client-side navigations won't generate pageviews. Handle this with Fathom's SPA support (a data-spa style attribute or a manual fathom.trackPageview() call in your router's onFinish/navigate hook) — and make sure you only call it once per navigation, otherwise you double-count.

API access for dashboards. The package wraps the Fathom API; requests are grouped per resource (sites, aggregations, events) and sent through the underlying HTTP connector. The integration tip that matters: cache aggressively. Fathom's API has rate limits and aggregation queries are expensive. Wrap every call:

$stats = Cache::remember("fathom.visitors.{$siteId}", now()->addMinutes(15), function () use ($siteId) {
    // send the aggregation request for the last 30 days
});

Fifteen minutes is plenty for an internal dashboard and it turns a rate-limit problem into a non-problem.

Scheduled reporting. Pull daily/weekly numbers in a console command and push them to Slack or email, rather than having humans refresh a dashboard. This is where the API wrapper earns its keep — a scheduled command is far more reliable than a live page that hits the API on every request.

Multi-site. If you run several properties, pull the site ID from config per-application and pass it explicitly to API calls rather than relying on the global default. For the script tag, pass the site ID as a component attribute so a single shared layout can serve multiple sites.

Gotchas and Tips

The script is public, the token is not. It is easy to accidentally leak the API token by putting it in a view or an Inertia shared prop. Keep the token server-side only; the component should never need it.

Ad-blockers will hide your traffic. Expect 10–30% undercount on consumer-facing sites. That's inherent to all client-side analytics and not a bug in the package. Don't chase the discrepancy with your server logs.

The defer attribute matters. The Fathom script is small, but if you inline it in <head> without defer, you can block first paint. The component should already emit it correctly — verify in the rendered HTML rather than assuming.

Testing. Because the package talks over HTTP, you can fake it in tests with Laravel's Http::fake() or, if it uses Saloon under the hood, a Saloon MockClient. Prefer this over hitting the real API in CI. Also assert on the rendered Blade output (assertSee('cdn.usefathom.com')) to catch the component being accidentally removed from a layout.

Publish the views before you fight them. If you need to change attributes, add a data- hook, or alter the script URL, publish the package views rather than string-replacing rendered HTML in a middleware.

Custom domains. Fathom supports serving the script from your own subdomain to reduce blocker interference. This is a config change (script_url), not a code change — but you must set up the DNS/CNAME first or the tag silently fails.

Version skew on the API client. The wrapper tracks Fathom's API; Fathom occasionally changes response shapes. Pin the package version, and keep a single thin service class between your app and the package so an upstream change is a one-file fix rather than a grep across your codebase. That layer is also where the caching lives, which is the extension point you'll actually use most.

Rate limits are per-token, not per-site. If you have multiple apps sharing one token, budget the calls globally, not per application.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource