Technical reference for implementing Google Analytics 4 (GA4) on HubSpot CMS Hub, covering the native GA4 setting, HubL template code injection, HubSpot form event listeners, CTA tracking via postMessage, and CRM contact property integration.
How GA4 Works on HubSpot
HubSpot CMS renders pages server-side and already loads its own tracking script (hs-analytics.js) on every page. GA4 runs alongside the HubSpot tracking pixel:
- Native integration (Settings > Tracking & Analytics > Tracking Code): HubSpot injects
gtag.jsinto the<head>of all HubSpot-hosted pages (CMS pages, landing pages, blog posts). The Measurement ID is stored in HubSpot account settings and applied automatically during page rendering. This method handles basicpage_viewevents. - Custom code (Site Header HTML): For advanced implementations, you add gtag code via Settings > Website > Pages > Site Header HTML or within individual HubL templates using
{{ require_js }}or raw<script>blocks. HubL personalization tokens ({{ contact.email }},{{ contact.vid }},{{ contact.lifecyclestage }}) resolve server-side, allowing you to pass CRM contact properties directly into GA4 user properties. - HubSpot forms use
postMessage: HubSpot forms render in iframes. Form submission events are communicated to the parent page viawindow.postMessagewithevent.data.type === 'hsFormCallback'. GA4 form tracking requires amessageevent listener on the parent window to interceptonFormSubmittedcallbacks. - CTAs: HubSpot CTAs render as anchor tags wrapped in
.hs-cta-wrappercontainers. They do not emit custom events -- tracking requires DOM event listeners on the CTA elements.
HubSpot's CDN aggressively caches pages. After modifying tracking code, you must clear the CDN cache via Settings > Advanced > Clear Cache for changes to take effect. HubSpot's consent banner (_hsp privacy API) integrates with GA4 Consent Mode v2 through the addPrivacyConsentListener callback.
Installation Methods
Method 1: Native HubSpot Integration (Recommended)
HubSpot offers built-in GA4 integration for seamless tracking.
Step 1: Access Tracking Code Settings
- Log in to your HubSpot account
- Navigate to Settings (gear icon in top navigation)
- In the left sidebar, go to Tracking & Analytics > Tracking Code
Step 2: Add GA4 Tracking
- Scroll to the Google Analytics section
- Click Add Google Analytics
- Enter your GA4 Measurement ID (format: G-XXXXXXXXXX)
- Select tracking options:
- Track all HubSpot-hosted pages
- Track HubSpot forms
- Track CTAs
Step 3: Configure Domain Settings
- In Tracking & Analytics, go to Domains & URLs
- Ensure your domain is properly configured
- Verify SSL certificate is active
- Save settings
Method 2: Custom Code Installation
For advanced implementations or custom tracking requirements.
Step 1: Add Code to Site Header
- Navigate to Settings > Website > Pages
- Click the Templates tab
- Select your active template
- Click Edit > Settings > Advanced Options
- Add to Site Header HTML:
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX', {
'send_page_view': true,
'cookie_flags': 'SameSite=None;Secure'
});
</script>
Step 2: Apply to All Pages
For site-wide tracking without template editing:
- Go to Settings > Website > Pages
- Click Site Header HTML in the header section
- Paste the GA4 code
- Click Save
Configuration Options
HubSpot-Specific Tracking
Leverage HubSpot data in GA4:
<script>
gtag('config', 'G-XXXXXXXXXX', {
'custom_map': {
'dimension1': 'hubspot_contact_id',
'dimension2': 'lifecycle_stage',
'dimension3': 'persona'
}
});
// Send HubSpot contact data
var _hsq = window._hsq = window._hsq || [];
_hsq.push(['identify', {
email: '{{ contact.email }}',
id: '{{ contact.vid }}'
}]);
// Track contact properties in GA4
if (typeof {{ contact.vid }} !== 'undefined') {
gtag('set', 'user_properties', {
'hubspot_contact_id': '{{ contact.vid }}',
'lifecycle_stage': '{{ contact.lifecyclestage }}',
'lead_status': '{{ contact.hs_lead_status }}'
});
}
</script>
Cross-Domain Tracking
For tracking across multiple HubSpot domains:
<script>
gtag('config', 'G-XXXXXXXXXX', {
'linker': {
'domains': ['www.yoursite.com', 'blog.yoursite.com', 'shop.yoursite.com']
}
});
</script>
Privacy and Consent
Implement cookie consent with HubSpot's privacy tools:
<script>
// Check HubSpot cookie consent
var _hsp = window._hsp = window._hsp || [];
_hsp.push(['addPrivacyConsentListener', function(consent) {
if (consent.categories.analytics) {
gtag('consent', 'update', {
'analytics_storage': 'granted'
});
} else {
gtag('consent', 'update', {
'analytics_storage': 'denied'
});
}
}]);
// Set default consent state
gtag('consent', 'default', {
'analytics_storage': 'denied',
'wait_for_update': 500
});
</script>
Event Tracking
HubSpot Form Submissions
Automatically track form completions:
<script>
window.addEventListener('message', function(event) {
if(event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormSubmitted') {
gtag('event', 'form_submit', {
'form_id': event.data.id,
'form_name': event.data.data.formName || 'HubSpot Form',
'page_path': window.location.pathname
});
}
});
</script>
CTA Click Tracking
Track HubSpot CTA clicks:
<script>
document.addEventListener('DOMContentLoaded', function() {
// Track HubSpot CTA clicks
var ctas = document.querySelectorAll('.hs-cta-wrapper a');
ctas.forEach(function(cta) {
cta.addEventListener('click', function() {
var ctaId = this.getAttribute('data-hs-cta-id');
var ctaText = this.textContent.trim();
gtag('event', 'cta_click', {
'cta_id': ctaId,
'cta_text': ctaText,
'page_location': window.location.href
});
});
});
});
</script>
Meeting/Demo Bookings
Track meeting scheduler interactions:
<script>
// HubSpot Meetings tracking
var _hsq = window._hsq = window._hsq || [];
_hsq.push(['trackCustomBehavioralEvent', {
name: 'pe22175099_meeting_booked',
properties: {
meeting_type: 'demo',
meeting_link: window.location.href
}
}]);
// Also send to GA4
gtag('event', 'meeting_booked', {
'meeting_type': 'demo',
'value': 100, // Estimated value of a demo
'currency': 'USD'
});
</script>
Blog Engagement
Track blog post interactions on HubSpot blogs:
<script>
// Track blog reading time
var startTime = new Date().getTime();
window.addEventListener('beforeunload', function() {
var timeSpent = Math.round((new Date().getTime() - startTime) / 1000);
if (timeSpent > 10) { // Only track if more than 10 seconds
gtag('event', 'blog_engagement', {
'time_on_page': timeSpent,
'blog_title': document.title,
'blog_category': '{{ content.blog_post_tags }}'
});
}
});
// Track scroll depth
var scrollTracked = {25: false, 50: false, 75: false, 100: false};
window.addEventListener('scroll', function() {
var scrollPercent = Math.round((window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100);
Object.keys(scrollTracked).forEach(function(threshold) {
if (scrollPercent >= threshold && !scrollTracked[threshold]) {
gtag('event', 'scroll', {
'percent_scrolled': threshold,
'page_type': 'blog_post'
});
scrollTracked[threshold] = true;
}
});
});
</script>
Lead Tracking Integration
Connect GA4 with HubSpot CRM
Pass contact information between systems:
<script>
// Get HubSpot contact info
var _hsq = window._hsq = window._hsq || [];
_hsq.push(['identify', {
email: '{{ contact.email }}',
firstname: '{{ contact.firstname }}',
lastname: '{{ contact.lastname }}'
}]);
// Send to GA4 as User ID
if ('{{ contact.vid }}' !== '') {
gtag('config', 'G-XXXXXXXXXX', {
'user_id': '{{ contact.vid }}',
'user_properties': {
'lifecycle_stage': '{{ contact.lifecyclestage }}',
'lead_score': '{{ contact.hs_lead_score }}',
'persona': '{{ contact.persona }}'
}
});
}
</script>
Deal Stage Tracking
Track prospect progression through sales pipeline:
<script>
// Track deal stage changes
gtag('event', 'deal_stage_change', {
'contact_id': '{{ contact.vid }}',
'old_stage': '{{ previous_deal_stage }}',
'new_stage': '{{ current_deal_stage }}',
'deal_value': {{ deal_amount }}
});
</script>
Landing Page Tracking
Landing Page Conversions
Track landing page form submissions and goals:
<script>
// Track landing page conversion
window.addEventListener('message', function(event) {
if(event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormSubmitted') {
gtag('event', 'conversion', {
'send_to': 'G-XXXXXXXXXX',
'event_category': 'Landing Page',
'event_label': document.title,
'value': 1
});
// Track as lead generation
gtag('event', 'generate_lead', {
'page_path': window.location.pathname,
'campaign_source': '{{ contact.hs_analytics_source }}',
'campaign_medium': '{{ contact.hs_analytics_source_data_1 }}'
});
}
});
</script>
Troubleshooting
Tracking Code Not Firing
Issue: GA4 not collecting data from HubSpot pages
Solutions:
- Verify GA4 Measurement ID is correct in HubSpot settings
- Check that tracking code is published (not in draft)
- Clear HubSpot CDN cache: Settings > Advanced > Clear Cache
- Ensure domain is properly connected in HubSpot
- Test in incognito mode to avoid browser cache issues
- Verify SSL certificate is active and valid
Duplicate Tracking
Issue: Page views counted twice
Solutions:
- Check if GA4 is added in both native settings AND custom code
- Remove GA4 from template if using native integration
- Verify GTM isn't also loading GA4
- Check for GA4 code in custom modules
- Review all tracking code locations in HubSpot
Form Submissions Not Tracking
Issue: HubSpot form submits not appearing in GA4
Solutions:
- Verify form submission event listener is properly configured
- Check browser console for JavaScript errors
- Test form in real-time with GA4 DebugView
- Ensure form isn't in an iframe blocking events
- Verify message event listener is on correct domain
- Check HubSpot form settings allow tracking
Contact Data Not Syncing
Issue: HubSpot contact properties not appearing in GA4
Solutions:
- Verify contact personalization tokens are correct
- Check that contacts have the required properties populated
- Ensure privacy settings allow contact identification
- Test with known contacts that have complete data
- Verify custom dimensions are set up in GA4 property
- Check HubSpot contact merge settings
Cross-Domain Tracking Issues
Issue: Sessions breaking across HubSpot subdomains
Solutions:
- Verify all domains are listed in linker configuration
- Check that GA4 Measurement ID is the same across domains
- Ensure cookies aren't blocked between domains
- Test with Chrome DevTools to verify linker parameter
- Check referral exclusion list in GA4
- Verify SSL is enabled on all domains
Performance Impact
Issue: Page load speed decreased after GA4 installation
Solutions:
- Ensure async attribute is on the gtag.js script
- Use HubSpot's native integration instead of custom code
- Minimize custom event tracking on page load
- Implement lazy loading for non-critical tracking
- Consider using Google Tag Manager for better performance
- Enable HubSpot's CDN and caching features
Verification and Testing
Test GA4 Integration
- Real-Time Reports: Visit your site and check GA4 Real-time view
- Form Testing: Submit a test form and verify event fires
- CTA Testing: Click CTAs and confirm in GA4 DebugView
- HubSpot Preview: Use HubSpot's preview mode to test tracking
Enable Debug Mode
Add debug parameter to your tracking:
<script>
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
</script>
Verify HubSpot Integration
- Check Reports > Analytics Tools in HubSpot
- Verify GA4 is listed under connected accounts
- Test contact property tracking with known contacts
- Review HubSpot form analytics for submission counts
Best Practices
Data Layer Integration
Combine HubSpot and GA4 data layers:
<script>
// Initialize both data layers
window.dataLayer = window.dataLayer || [];
window._hsq = window._hsq || [];
// Push HubSpot data to GA4 dataLayer
_hsq.push(['addIdentityListener', function(hstc, hssc, hsfp) {
dataLayer.push({
'event': 'hubspot_identity',
'hubspot_cookie': hstc,
'visitor_token': hsfp
});
}]);
</script>
Campaign Tracking
Ensure UTM parameters are preserved:
<script>
gtag('config', 'G-XXXXXXXXXX', {
'campaign_source': '{{ contact.hs_analytics_source }}',
'campaign_medium': '{{ contact.hs_analytics_source_data_1 }}',
'campaign_name': '{{ contact.hs_analytics_source_data_2 }}'
});
</script>