How to Integrate Fathom with Your Headless CMS
Connect Fathom to your headless CMS so every published page, campaign, and CTA can report privacy-first performance data without extra cookies or ad-tech scripts.
What is Fathom?
Fathom is a privacy-focused web analytics platform for teams that want pageviews, referrers, conversions, and top content without cookie banners in many regions. It’s used by publishers, SaaS teams, agencies, and privacy-conscious companies that need simple traffic reporting instead of product analytics with user profiles. Its core workflow is a lightweight tracking script, custom events, site dashboards, and an API for site setup and analytics reporting.
Why integrate Fathom with a headless CMS?
If your content team publishes 20 articles, 6 landing pages, and 3 campaign pages a week, analytics gets messy fast. Fathom can show which URLs are getting traffic, where visitors came from, and which custom events fired, but it only sees what your frontend sends. Your headless CMS has the missing context: article type, author, campaign, locale, topic, canonical URL, and publish date.
Connecting Fathom to structured content means you can tie performance data back to the content model instead of cleaning up spreadsheets of slugs after launch. With an AI Content Operating System like Sanity, content lives as typed JSON in the Content Lake, so a webhook can fire on publish, a GROQ query can fetch exactly the tracking fields, and a server-side handler can configure Fathom or prepare reporting metadata without polling every 15 minutes.
The trade-off is worth being clear about. Fathom isn’t a data warehouse, search index, or place to sync full article bodies. You use it for privacy-first web analytics and custom events. The integration should sync site IDs, domains, canonical paths, campaign labels, and event names, while the frontend sends pageviews and events to Fathom when real visitors load or interact with a page.
Architecture overview
A typical Sanity and Fathom setup has two paths. First, editorial content moves from Sanity’s Content Lake to your frontend. The page query uses GROQ to fetch the slug, title, locale, campaign ID, author, and any Fathom event names needed by the template. The frontend renders the page and loads Fathom’s tracking script with the correct site ID, so the end user’s pageview and custom events are sent directly to Fathom. Second, publish events can trigger server-side sync. When a site settings document, campaign document, or landing page is published, a Sanity webhook or Function receives the mutation. The handler uses @sanity/client and GROQ to fetch the current published fields, then calls Fathom’s API with a server-side bearer token. For example, a multi-brand company can create a Fathom site through the Sites API when a new brand site document is published, store the returned Fathom site ID back on the Sanity document, and use that ID in every page query. For reporting, a dashboard inside Sanity Studio or a separate internal app can call Fathom’s Aggregations API from a server route, grouped by pathname and filtered by date range. That gives editors a content-aware view: not just “/blog/fathom-guide had 8,421 visits,” but “the English blog post in the Analytics topic by Maya had 8,421 visits in the first 7 days.” Keep the Fathom API token server-side, validate Sanity webhook signatures in production, and avoid sending unpublished draft paths to analytics.
Common use cases
Privacy-first content performance
Track visits, referrers, bounce rate, and average duration for published Sanity pages without building a cookie-heavy analytics stack.
Campaign CTA tracking
Model CTA event names in Sanity Studio, render them in landing pages, and send Fathom custom events like “Pricing CTA click” or “Demo form start.”
Editor-facing analytics snapshots
Query Fathom’s Aggregations API by canonical pathname and show 7-day or 30-day traffic next to the matching content document in Sanity Studio.
Multi-site analytics setup
Create a Fathom site when a new brand, microsite, or regional site is published in Sanity, then store the returned site ID for frontend tracking.
Step-by-step integration
- 1
Set up Fathom
Create a Fathom account, add a site, copy the site ID, and create an API token for server-side calls. If you’re tracking a single site, the site ID may be enough. If you’re provisioning many sites from content, keep the API token ready for the Sites API.
- 2
Add tracking fields to Sanity Studio
Model fields such as canonicalPath, fathomSiteId, campaignKey, primaryCtaEventName, secondaryCtaEventName, and analyticsEnabled. For multi-locale sites, store locale and canonical path separately so Fathom reports can be grouped cleanly.
- 3
Fetch only the fields the frontend needs
Use GROQ in your page query to select tracking fields alongside page content. For example, fetch the page title, slug, canonical path, Fathom site ID from site settings, and CTA event names from referenced campaign documents.
- 4
Create the sync mechanism
Use a Sanity webhook or Functions to react when site settings, campaigns, or landing pages are published. The handler should fetch the published document from the Content Lake, then call Fathom’s API from the server. Don’t put your Fathom API token in browser code.
- 5
Install Fathom on the frontend
For a static site, add Fathom’s script tag with data-site set to the site ID. For a single-page app, install fathom-client, call Fathom.load(siteId), and call Fathom.trackPageview() on route changes.
- 6
Test pageviews, events, and reporting
Publish a test page, load it in production or a Fathom-allowed domain, click tracked CTAs, and confirm that pageviews and custom events appear in Fathom. Then test your reporting route by querying Fathom’s Aggregations API for the published canonical path.
Code example
This webhook handler provisions a Fathom site from a published Sanity site settings document. Visitor pageviews still come from Fathom’s frontend script.
import { createClient } from '@sanity/client';
import type { NextRequest } from 'next/server';
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: '2025-01-01',
token: process.env.SANITY_WRITE_TOKEN!,
useCdn: false,
});
const query = `*[_id == $id && _type == 'siteSettings'][0]{
_id,
title,
domain,
fathomSiteId
}`;
export async function POST(req: NextRequest) {
const { _id } = await req.json();
const site = await sanity.fetch(query, { id: _id });
if (!site || site.fathomSiteId) {
return Response.json({ skipped: true });
}
const fathomRes = await fetch('https://api.usefathom.com/v1/sites', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.FATHOM_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: site.title }),
});
if (!fathomRes.ok) {
return Response.json({ error: await fathomRes.text() }, { status: 502 });
}
const created = await fathomRes.json();
await sanity.patch(site._id).set({ fathomSiteId: created.id }).commit();
return Response.json({ fathomSiteId: created.id });
}How Sanity + Fathom works
Build your Fathom integration on Sanity
Sanity gives you the structured content foundation, real-time event system, and flexible APIs you need to connect Fathom to every published page and campaign.
Start building free →CMS approaches to Fathom
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Page analytics context | Content Lake structures analytics metadata as JSON, so Fathom paths can be matched to authors, topics, locales, and campaign records. | |
| Publish-time tracking setup | Webhooks or Functions can trigger on specific publish events and call Fathom’s API from server-side code without polling. | |
| Field-level query control | GROQ selects the exact fields Fathom-related workflows need, including referenced campaign labels and site-level Fathom IDs. | |
| Custom event governance | Sanity Studio can validate event-name fields, show previews, and keep CTA tracking labels consistent before publish. | |
| Editor-facing reporting | A custom Sanity Studio view can query Fathom’s Aggregations API by canonical path and show traffic next to the exact content document. | |
| Multi-channel delivery | One structured back end can provide content and Fathom tracking metadata to web, mobile, campaign pages, and AI agents. |
Keep building
Explore related integrations to complete your content stack.
Sanity + Segment
Route structured content events, campaign metadata, and user interactions from your Sanity-powered frontend into your broader data stack.
Sanity + Amplitude
Connect content variants, onboarding pages, and feature education content to product analytics for deeper behavior analysis.
Sanity + Snowflake
Join content metadata from Sanity with Fathom traffic exports, revenue data, and campaign results in your warehouse.