Microsoft Clarity • Google Analytics 4 • Google Tag Manager
Navigate: Space or → | Overview: ESC | Fullscreen: F
Without data, you're just another person with an opinion
Decisions based on assumptions
Data-driven decisions
See exactly what your users are doing
1 Go to clarity.microsoft.com → Sign in → Create new project
2 Enter your website URL → Get your unique project ID
3
Add this script to your HTML <head>:
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "YOUR_PROJECT_ID");
</script>
<!-- telemetry-demo/index.html -->
<script type="text/javascript">
(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "t1ori1zqey");
</script>
View in GitHub →
t1ori1zqey - unique to each project// src/utils/tracking.ts
export const trackClarityCustomEvent = (
eventName: string,
customTag?: Record<string, any>
) => {
if (typeof window !== 'undefined' && window.clarity) {
window.clarity('set', eventName, customTag);
console.log('🔍 Clarity Event:', eventName, customTag);
}
};
View in GitHub →
// src/pages/HomePage.tsx
const handleSignupClick = () => {
// Send to Google Analytics
trackGTMEvent('sign_up', {
method: 'email',
event_category: 'engagement',
});
// Tag this Clarity session
trackClarityCustomEvent('SignupAttempt', {
page: 'home',
method: 'email'
});
};
View in GitHub →
Filter: SignupAttempt = true
→ See only users who tried to sign up
• What did they do before?
• Did they complete it?
• Where did they struggle?
Measure everything with numbers
| Question | Clarity Answer | GA4 Answer |
|---|---|---|
| User confusion? | Watch recording of rage clicks | 15% drop-off at step 3 |
| Feature popular? | See heatmap of button | 2,341 clicks this week (+12%) |
| Performance issue? | User waiting on slow page | Avg load: 3.2s (P95: 7.1s) |
✅ Simple and direct | ❌ Code changes for updates
window.gtag('event', 'purchase', {...})
✅ No-code updates | ✅ Marketing autonomy | ✅ Multiple tools
window.dataLayer.push({event: 'purchase', ...})
✅ Type-safe | ✅ React hooks | ❌ Only for React
ReactGA.event('purchase', {...})
<!-- Add to <head> -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;
f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- Add after <body> -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
GTM-MT9MWSLQindex.html lines 9-15 and 27-30dataLayer array// src/utils/tracking.ts
export const trackGTMEvent = (
eventName: string,
parameters?: Record<string, any>
) => {
if (typeof window !== 'undefined') {
// Send to GTM via dataLayer
if (window.dataLayer) {
window.dataLayer.push({
event: eventName,
...parameters
});
console.log('GTM Event:', eventName, parameters);
}
// Also send directly to GA4
if (window.gtag) {
window.gtag('event', eventName, parameters);
console.log('GA4 Direct:', eventName, parameters);
}
}
};
View in GitHub →
Turn raw data into business insights
{
// What happened?
event_name: "button_click",
// Standard GA4 parameters
event_category: "engagement",
event_label: "hero_cta",
value: 1,
// Your custom properties
button_text: "Get Started",
page_section: "hero",
user_type: "free",
ab_test_variant: "variant_b",
session_duration_seconds: 145
}
// src/pages/HomePage.tsx
const handleButtonClick = () => {
const newCount = clickCount + 1;
setClickCount(newCount);
trackGTMEvent('button_click', {
// Standard parameters
event_category: 'engagement',
event_label: 'home_page_button',
value: newCount,
// Custom business context
button_text: 'Track Engagement Event',
page_section: 'hero',
click_count: newCount,
session_duration: Math.floor((Date.now() - startTime) / 1000),
viewport_width: window.innerWidth,
is_mobile: window.innerWidth < 768
});
};
View in GitHub →
// src/pages/HomePage.tsx
trackGTMEvent('purchase', {
// Standard GA4 e-commerce parameters
transaction_id: `order_${Date.now()}`,
value: 99.99,
currency: 'USD',
items: [{
item_id: 'SKU123',
item_name: 'Premium Plan',
item_category: 'Subscription',
price: 99.99,
quantity: 1
}],
// Custom business properties
payment_type: 'credit_card',
coupon_code: 'SAVE20',
is_first_purchase: true,
referral_source: 'email_campaign',
checkout_duration_seconds: 45
});
"1,000 button clicks happened"
"Premium users on mobile clicked 3x more than free users on desktop"
Production-ready code patterns
// src/App.tsx
function AppContent() {
const location = useLocation();
useEffect(() => {
const pageTitles: Record<string, string> = {
'/': 'Home',
'/about': 'About',
'/custom-properties': 'Custom Properties'
};
const pageTitle = pageTitles[location.pathname] || 'Unknown';
// Track page view with context
trackPageView(location.pathname, pageTitle);
}, [location]); // Runs on every route change
return <Routes>...</Routes>;
}
View in GitHub →
export const safeTrackEvent = (
eventName: string,
properties?: any
) => {
try {
// Check environment
if (typeof window === 'undefined') return;
// Check if scripts loaded
if (!window.dataLayer && !window.gtag) {
console.warn('Analytics not loaded');
return;
}
// Send event
trackGTMEvent(eventName, {
...properties,
timestamp: new Date().toISOString(),
page_url: window.location.href
});
} catch (error) {
// Never break the app for analytics
console.error('Tracking failed:', error);
}
};
// Check if loaded
console.log(window.dataLayer); // GTM events
console.log(window.clarity); // Clarity object
console.log(window.gtag); // GA4 function
// Manual test
window.dataLayer.push({
event: 'test_event',
test_param: 'test_value'
});
| Mistake | Problem | Solution |
|---|---|---|
| Tracking before load | Events lost | Check if (window.gtag) |
| Too many properties | GA4 limit: 25 per event | Send only essential data |
| PII in events | Privacy violations | Hash/remove emails, names |
| No error handling | App crashes | Wrap in try-catch |
| Duplicate tracking | Inflated metrics | Centralize tracking calls |
From events to insights
-- Query your custom properties in BigQuery
SELECT
event_name,
(SELECT value.string_value
FROM UNNEST(event_params)
WHERE key = 'button_text') as button_text,
(SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'click_count') as clicks,
COUNT(*) as total_events
FROM `project.analytics_123456.events_*`
WHERE event_name = 'button_click'
AND _TABLE_SUFFIX BETWEEN '20240101' AND '20240131'
GROUP BY event_name, button_text, clicks;
telemetry-demo/
├── index.html # Analytics scripts
├── src/
│ ├── utils/
│ │ ├── tracking.ts # Core tracking functions
│ │ └── tracking-comparison.ts # All approaches
│ ├── pages/
│ │ ├── HomePage.tsx # Basic events
│ │ ├── AboutPage.tsx # Content tracking
│ │ └── CustomPropertiesPage.tsx # Advanced examples
│ └── App.tsx # Route tracking
└── public/
└── docs/ # This presentation
Visit any page and open console to see events being tracked in real-time
Begin with Clarity for visual insights, then add GA4 for metrics
If it affects business decisions, track it with proper context
Transform generic events into specific business insights
Use DebugView, console logs, and test environments
Analytics should fail silently, not crash your app
Questions?
Repository: github.com/ChakshuGautam/telemetry-ga-clarity
Contact: chakshu@samagragovernance.in
Press ESC for slide overview