Overview
Unlike traditional analytics platforms that use complex data layers (like Google Tag Manager's dataLayer), Fathom Analytics follows a simpler, privacy-first approach. Fathom doesn't support a persistent data layer or user properties, maintaining its commitment to privacy and simplicity.
This guide explains how to structure your data collection within Fathom's constraints and implement tracking patterns that provide valuable insights while respecting user privacy.
Understanding Fathom's Data Model
What Fathom Collects Automatically
Pageview Data (Collected by Default):
- Page URL
- Page title
- Referrer source
- Device type (desktop, mobile, tablet)
- Browser type
- Operating system
- Country (from IP, then discarded)
- Timestamp
What Fathom Does NOT Collect:
- Cookies
- User IDs or identifiers
- IP addresses (used for geolocation, then discarded)
- Personal information
- Cross-site tracking data
- Browsing history
- Device fingerprints
Privacy-First Constraints
Fathom intentionally limits data collection to protect privacy:
No Persistent User Properties:
- Cannot store user attributes across sessions
- Cannot identify returning users individually
- Cannot build user profiles
- Cannot track user journeys at individual level
No Traditional Data Layer:
- No
dataLayer.push()equivalent - No global object for storing context
- No pre-population of event data
Aggregate Data Only:
- All data is anonymized and aggregated
- Individual user behavior not tracked
- Session-level continuity not maintained across visits
Implementing Context with Goals
Since Fathom doesn't support a traditional data layer, use goal-based tracking with naming conventions to add context.
Pattern 1: Descriptive Goal Names
Create goals with contextual information in the name:
Examples:
// Product category tracking
fathom.trackGoal('PROD_ELECTRONICS', 0);
fathom.trackGoal('PROD_CLOTHING', 0);
// User type tracking
fathom.trackGoal('SIGNUP_FREE', 0);
fathom.trackGoal('SIGNUP_TRIAL', 0);
fathom.trackGoal('SIGNUP_PAID', 0);
// Feature usage
fathom.trackGoal('FEAT_EXPORT_CSV', 0);
fathom.trackGoal('FEAT_EXPORT_PDF', 0);
// Content type
fathom.trackGoal('CONTENT_ARTICLE', 0);
fathom.trackGoal('CONTENT_VIDEO', 0);
fathom.trackGoal('CONTENT_PODCAST', 0);
Dashboard organization: Goals appear as separate items, allowing you to see:
- How many users exported CSV vs PDF
- Free vs trial vs paid signups
- Electronics vs clothing product views
Pattern 2: Programmatic Goal Selection
Use JavaScript to dynamically select goals based on page context:
// Product category from page metadata
const category = document.querySelector('[data-category]')?.dataset.category;
if (category) {
const goalId = {
'electronics': 'PROD_ELEC',
'clothing': 'PROD_CLTH',
'home': 'PROD_HOME'
}[category];
if (goalId) {
fathom.trackGoal(goalId, 0);
}
}
// User plan from logged-in state
function trackSignupByPlan(planType) {
const goalIds = {
'free': 'SIGNUP_FREE',
'trial': 'SIGNUP_TRIAL',
'pro': 'SIGNUP_PRO',
'enterprise': 'SIGNUP_ENT'
};
const goalId = goalIds[planType];
if (goalId) {
fathom.trackGoal(goalId, 0);
}
}
// Usage
trackSignupByPlan('trial');
Pattern 3: Combined Context Goals
Create compound goals for multi-dimensional tracking:
// Platform + Action
fathom.trackGoal('IOS_DOWNLOAD', 0);
fathom.trackGoal('ANDROID_DOWNLOAD', 0);
fathom.trackGoal('WEB_SIGNUP', 0);
// Source + Conversion
fathom.trackGoal('GOOGLE_CONVERT', 0);
fathom.trackGoal('TWITTER_CONVERT', 0);
fathom.trackGoal('EMAIL_CONVERT', 0);
// Price tier + Purchase
function trackPurchase(tier, amount) {
const goalIds = {
'basic': 'PURCHASE_BASIC',
'pro': 'PURCHASE_PRO',
'enterprise': 'PURCHASE_ENT'
};
const goalId = goalIds[tier];
if (goalId) {
fathom.trackGoal(goalId, amount);
}
}
Revenue Tracking as Data Context
Revenue values provide an additional dimension of data:
Basic Revenue Tracking
// Track purchase with revenue
const orderTotal = 149.99;
const cents = Math.round(orderTotal * 100);
fathom.trackGoal('PURCHASE', cents);
Revenue by Product Category
// Create separate purchase goals per category
function trackPurchaseByCategory(category, amount) {
const goalIds = {
'electronics': 'PURCH_ELEC',
'clothing': 'PURCH_CLTH',
'home': 'PURCH_HOME'
};
const cents = Math.round(amount * 100);
const goalId = goalIds[category];
if (goalId) {
fathom.trackGoal(goalId, cents);
}
}
// Usage
trackPurchaseByCategory('electronics', 299.99);
Revenue by Plan Tier
// Track subscription by plan
function trackSubscription(plan, monthlyPrice) {
const goalIds = {
'basic': 'SUB_BASIC',
'pro': 'SUB_PRO',
'enterprise': 'SUB_ENT'
};
const cents = Math.round(monthlyPrice * 100);
const goalId = goalIds[plan];
if (goalId) {
fathom.trackGoal(goalId, cents);
}
}
// Usage
trackSubscription('pro', 49.99);
Page Metadata for Context
Use page metadata to inform goal tracking:
HTML Data Attributes
<!-- Product page -->
<body data-page-type="product" data-category="electronics" data-price-tier="premium">
<script>
// Extract metadata
const pageType = document.body.dataset.pageType;
const category = document.body.dataset.category;
const priceTier = document.body.dataset.priceTier;
// Track contextual goal
if (pageType === 'product' && category) {
const goalId = `PRODUCT_${category.toUpperCase()}`;
fathom.trackGoal(goalId, 0);
}
</script>
Meta Tags
<meta name="analytics:category" content="electronics">
<meta name="analytics:type" content="product">
<script>
const category = document.querySelector('meta[name="analytics:category"]')?.content;
const type = document.querySelector('meta[name="analytics:type"]')?.content;
if (category && type === 'product') {
fathom.trackGoal(`PROD_${category.toUpperCase()}`, 0);
}
</script>
Session Context (Client-Side Only)
While Fathom doesn't persist user data, you can use client-side storage for session-level context:
sessionStorage for Temporary Context
// Track source on landing
const urlParams = new URLSearchParams(window.location.search);
const source = urlParams.get('utm_source');
if (source) {
sessionStorage.setItem('landing_source', source);
}
// Use source context on conversion
function trackConversion() {
const source = sessionStorage.getItem('landing_source') || 'direct';
const goalIds = {
'google': 'CONV_GOOGLE',
'twitter': 'CONV_TWITTER',
'email': 'CONV_EMAIL',
'direct': 'CONV_DIRECT'
};
const goalId = goalIds[source];
if (goalId) {
fathom.trackGoal(goalId, 0);
}
}
Important: This data is client-side only and not sent to Fathom. It's used to determine which goal to fire.
localStorage for Persistent Client Context
// Store plan selection
function selectPlan(planType) {
localStorage.setItem('selected_plan', planType);
fathom.trackGoal(`PLAN_SELECTED_${planType.toUpperCase()}`, 0);
}
// Use on signup
function trackSignup() {
const plan = localStorage.getItem('selected_plan') || 'free';
const goalId = `SIGNUP_${plan.toUpperCase()}`;
fathom.trackGoal(goalId, 0);
// Clean up
localStorage.removeItem('selected_plan');
}
URL Parameters as Context
Use URL structure to add context without storing user data:
UTM Parameters
// Track campaign conversions
const urlParams = new URLSearchParams(window.location.search);
const campaign = urlParams.get('utm_campaign');
if (campaign) {
// Track that this conversion came from specific campaign
const goalId = `CONV_${campaign.toUpperCase().replace(/[^A-Z0-9]/g, '')}`;
fathom.trackGoal(goalId, 0);
}
Query String Context
<!-- Link with context -->
<a href="/signup?plan=pro">Sign Up for Pro</a>
<script>
// On signup page
const urlParams = new URLSearchParams(window.location.search);
const plan = urlParams.get('plan') || 'free';
// Track signup with plan context
const goalId = `SIGNUP_${plan.toUpperCase()}`;
fathom.trackGoal(goalId, 0);
</script>
Ecommerce Data Patterns
Product Data
// On product page
const productData = {
id: 'SKU12345',
category: 'electronics',
price: 299.99
};
// Track product view by category
fathom.trackGoal(`PRODVIEW_${productData.category.toUpperCase()}`, 0);
// Track add to cart with category context
function addToCart(product) {
fathom.trackGoal(`ADDCART_${product.category.toUpperCase()}`, 0);
}
Order Data
// After successful purchase
const orderData = {
total: 449.97,
items: [
{ category: 'electronics', price: 299.99 },
{ category: 'accessories', price: 149.98 }
],
shippingMethod: 'express'
};
// Track overall purchase
const cents = Math.round(orderData.total * 100);
fathom.trackGoal('PURCHASE', cents);
// Track by shipping method
const shippingGoal = `SHIP_${orderData.shippingMethod.toUpperCase()}`;
fathom.trackGoal(shippingGoal, 0);
// Track by dominant category
const dominantCategory = orderData.items.sort((a, b) => b.price - a.price)[0].category;
const categoryGoal = `PURCH_${dominantCategory.toUpperCase()}`;
fathom.trackGoal(categoryGoal, cents);
Event Sequencing Without User Tracking
Track funnel progression without identifying individuals:
Funnel Step Goals
// Landing page
fathom.trackGoal('FUNNEL_LANDING', 0);
// Pricing page
fathom.trackGoal('FUNNEL_PRICING', 0);
// Signup page
fathom.trackGoal('FUNNEL_SIGNUP', 0);
// Onboarding
fathom.trackGoal('FUNNEL_ONBOARD', 0);
// Conversion
fathom.trackGoal('FUNNEL_CONVERT', 4900);
Analysis: Compare goal counts to identify drop-off points:
- 1000 FUNNEL_LANDING
- 300 FUNNEL_PRICING (70% drop-off)
- 150 FUNNEL_SIGNUP (50% drop-off)
- 100 FUNNEL_ONBOARD (33% drop-off)
- 75 FUNNEL_CONVERT (25% drop-off)
Micro-Conversions
// Track engagement levels
fathom.trackGoal('ENGAGE_SCROLL_50', 0); // Scrolled 50%
fathom.trackGoal('ENGAGE_SCROLL_75', 0); // Scrolled 75%
fathom.trackGoal('ENGAGE_VIDEO_PLAY', 0); // Played video
fathom.trackGoal('ENGAGE_VIDEO_COMPLETE', 0); // Completed video
fathom.trackGoal('ENGAGE_CTA_CLICK', 0); // Clicked CTA
Centralized Tracking Configuration
Create a configuration object for organized tracking:
Tracking Configuration File
// analytics-config.js
export const FATHOM_GOALS = {
// Signups
SIGNUP_FREE: 'SIGNUP_FREE',
SIGNUP_TRIAL: 'SIGNUP_TRIAL',
SIGNUP_PRO: 'SIGNUP_PRO',
// Purchases
PURCHASE: 'PURCHASE',
PURCHASE_ELECTRONICS: 'PURCH_ELEC',
PURCHASE_CLOTHING: 'PURCH_CLTH',
// Features
FEATURE_EXPORT_CSV: 'FEAT_EXPORT_CSV',
FEATURE_EXPORT_PDF: 'FEAT_EXPORT_PDF',
// Funnel
FUNNEL_LANDING: 'FUNNEL_LANDING',
FUNNEL_PRICING: 'FUNNEL_PRICING',
FUNNEL_SIGNUP: 'FUNNEL_SIGNUP',
};
export function trackGoal(goalId, value = 0) {
if (window.fathom && typeof window.fathom.trackGoal === 'function') {
window.fathom.trackGoal(goalId, value);
}
}
export function trackPurchaseByCategory(category, amount) {
const categoryGoals = {
'electronics': FATHOM_GOALS.PURCHASE_ELECTRONICS,
'clothing': FATHOM_GOALS.PURCHASE_CLOTHING,
};
const goalId = categoryGoals[category] || FATHOM_GOALS.PURCHASE;
const cents = Math.round(amount * 100);
trackGoal(goalId, cents);
}
Usage
import { FATHOM_GOALS, trackGoal, trackPurchaseByCategory } from './analytics-config';
// Simple goal tracking
trackGoal(FATHOM_GOALS.SIGNUP_FREE, 0);
// Purchase with category context
trackPurchaseByCategory('electronics', 299.99);
Best Practices
Goal Naming Conventions
Use consistent patterns:
// Category prefix
'SIGNUP_*' // All signup-related goals
'PURCHASE_*' // All purchase-related goals
'FEATURE_*' // All feature usage goals
'FUNNEL_*' // All funnel step goals
// Examples
'SIGNUP_FREE'
'SIGNUP_TRIAL'
'PURCHASE_ELECTRONICS'
'PURCHASE_CLOTHING'
'FEATURE_EXPORT'
'FUNNEL_LANDING'
Documentation
Maintain a tracking dictionary:
# Fathom Goal Dictionary
## Signups
- SIGNUP_FREE (ID: FREE001) - Free plan signup
- SIGNUP_TRIAL (ID: TRIAL01) - Trial plan signup
- SIGNUP_PRO (ID: PRO0001) - Pro plan signup
## Purchases
- PURCHASE (ID: PURCHASE) - Any purchase (with revenue)
- PURCH_ELEC (ID: PRCHELEC) - Electronics purchase (with revenue)
- PURCH_CLTH (ID: PRCHCLTH) - Clothing purchase (with revenue)
## Funnel
- FUNNEL_LANDING (ID: FUNLAND1) - Landing page view
- FUNNEL_PRICING (ID: FUNPRIC1) - Pricing page view
- FUNNEL_SIGNUP (ID: FUNSIGN1) - Signup page view
Testing
Validate goal firing:
// Development mode logging
const isDev = process.env.NODE_ENV === 'development';
function trackGoalWithLogging(goalId, value = 0) {
if (isDev) {
console.log(`[Fathom] Goal: ${goalId}, Value: ${value}`);
}
if (window.fathom) {
window.fathom.trackGoal(goalId, value);
}
}
Privacy Compliance
Already compliant:
- No personal data collection needed
- No cookie consent required
- No user identification
- All data anonymous and aggregated
Don't try to circumvent privacy:
// Don't attempt to track individual users
// Violates Fathom's privacy principles
sessionStorage.setItem('user_id', userId); // Don't do this
// Track aggregate behavior only
fathom.trackGoal('USER_SIGNUP', 0);
Limitations to Understand
Cannot Do
- User-level journeys: Cannot track individual user paths
- Persistent user properties: Cannot store attributes across sessions
- Complex segmentation: Limited to goal-based tracking
- Behavior cohorts: Cannot group users by behavior patterns
- Multi-touch attribution: Cannot credit multiple touchpoints
Can Do
- Aggregate funnel analysis: Compare goal counts for drop-off analysis
- Category-based tracking: Use goals for different categories/types
- Revenue by segment: Track revenue for different product/plan types
- Campaign performance: Track conversions by source/campaign
- Feature adoption: Track feature usage through goals
Conclusion
While Fathom doesn't support traditional data layers or user properties, its goal-based approach provides valuable aggregate insights while maintaining privacy. By using:
- Descriptive goal naming for context
- Programmatic goal selection based on page metadata
- Revenue tracking for monetary context
- Client-side session storage for funnel tracking (without sending to Fathom)
- Centralized configuration for organized tracking
You can implement effective analytics that respect user privacy and provide actionable business insights.
Additional Resources: