HubSpot GTM Integration
Complete guide to setting up Google Tag Manager (GTM) on your HubSpot site for centralized tracking and tag management.
Getting Started
GTM Setup Guide
Step-by-step instructions for installing GTM on HubSpot.
Data Layer Implementation
Implement a comprehensive data layer for enhanced tracking capabilities.
Why Use GTM with HubSpot?
GTM provides powerful tag management benefits:
- Centralized Management: Control all tracking from one interface
- No Code Deploys: Add/modify tags without site changes
- Version Control: Track changes and roll back if needed
- Preview Mode: Test tags before publishing
- Advanced Triggers: Fire tags based on complex conditions
Prerequisites
Before installing GTM on HubSpot:
- HubSpot Marketing Hub (Professional or Enterprise recommended)
- Access to HubSpot Settings
- Website Pages permissions
- Google Tag Manager account created
- GTM Container ID (format: GTM-XXXXXXX)
- Understanding of HubSpot's tracking code system
Installation Methods
Method 1: HubSpot Tracking Code (Recommended)
HubSpot's tracking code settings provide the easiest implementation:
Site-Wide Installation:
- Log into your HubSpot account
- Navigate to Settings > Tracking & Analytics > Tracking Code
- Scroll to Add code for tracking & analytics section
- Click Head HTML tab
- Paste your GTM head code:
<!-- Google Tag Manager -->
<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-XXXXXXX');</script>
<!-- End Google Tag Manager -->
- Click Footer HTML tab
- Paste your GTM noscript code:
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
- Click Save to apply changes
Pros: Easy implementation, applies to all HubSpot pages, survives template updates Cons: Limited placement control, loads after HubSpot's own tracking
Method 2: Template Integration
For precise control, add GTM to HubSpot templates:
For Design Manager Templates:
- Navigate to Marketing > Files and Templates > Design Tools
- Open your template file (usually
page.htmlorblog-post.html) - Add GTM code in the
<head>section after{{ standard_header_includes }}:
{{ standard_header_includes }}
<!-- Google Tag Manager -->
<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-XXXXXXX');</script>
<!-- End Google Tag Manager -->
- Add noscript code after opening
<body>tag:
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
- Click Publish changes
Pros: Optimal placement, loads before other scripts, granular control Cons: Requires template editing, may need updates on template changes
Method 3: Module-Based Implementation
Create a custom module for GTM:
- In Design Tools, create a new module
- Name it "Google Tag Manager"
- Add Custom HTML field
- Insert GTM code in the module
- Add module to header or footer template sections
- Configure module visibility if needed
Container ID Configuration
Finding Your Container ID
- Log into Google Tag Manager
- Navigate to your container
- Container ID displays in the format GTM-XXXXXXX
- Copy the complete ID including prefix
Environment-Specific Containers
Use different containers for different HubSpot portals:
Production Portal:
- Container ID: GTM-PROD123
- Full conversion tracking
Sandbox/Test Portal:
- Container ID: GTM-TEST456
- Testing environment only
Implementation:
<script>
// Detect HubSpot portal environment
var gtmId = window.location.hostname.includes('hs-sites')
? 'GTM-TEST456' // Sandbox
: 'GTM-PROD123'; // Production
</script>
Data Layer Implementation
HubSpot-Specific Data Layer
Implement data layer using HubSpot's HubL variables:
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'portalId': '{{ portal_id }}',
'pageId': '{{ content.id }}',
'pageType': '{{ content_type }}',
'pageTitle': '{{ page_meta.html_title }}',
'pageUrl': '{{ content.absolute_url }}',
'language': '{{ content.language }}',
'authorId': '{{ content.blog_post_author.id }}',
'authorName': '{{ content.blog_post_author.display_name }}',
'publishDate': '{{ content.publish_date }}',
'campaign': '{{ request.query_dict.utm_campaign }}',
'source': '{{ request.query_dict.utm_source }}',
'medium': '{{ request.query_dict.utm_medium }}'
});
</script>
Blog Post Data Layer
For blog-specific tracking:
{% if content_type == 'blog-post' %}
<script>
dataLayer.push({
'event': 'blogPostView',
'blogPost': {
'id': '{{ content.id }}',
'title': '{{ content.name }}',
'author': '{{ content.blog_post_author.display_name }}',
'publishDate': '{{ content.publish_date }}',
'tags': [{% for tag in content.tag_list %}'{{ tag }}'{% if not loop.last %},{% endif %}{% endfor %}],
'category': '{{ content.blog_post_category.name }}'
}
});
</script>
{% endif %}
Form Submission Tracking
Track HubSpot form submissions:
<script>
window.addEventListener('message', function(event) {
if(event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormSubmit') {
dataLayer.push({
'event': 'formSubmission',
'formId': event.data.id,
'formGuid': event.data.guid,
'formType': 'hubspot'
});
}
});
</script>
CTA Click Tracking
<script>
document.addEventListener('click', function(e) {
if(e.target.closest('.cta_button')) {
var ctaButton = e.target.closest('.cta_button');
dataLayer.push({
'event': 'ctaClick',
'ctaId': ctaButton.getAttribute('data-cta-id'),
'ctaText': ctaButton.textContent.trim()
});
}
});
</script>
Smart Content Tracking
Track personalized content views:
<script>
{% if smart_content %}
dataLayer.push({
'event': 'smartContentView',
'contentVariation': '{{ smart_content.variation }}',
'targetingCriteria': '{{ smart_content.targeting }}'
});
{% endif %}
</script>
Common Triggers and Tags
Essential Triggers for HubSpot
Create these triggers in GTM:
All Pages Trigger
- Type: Page View
- Fires on: All Pages
Blog Post View Trigger
- Type: Custom Event
- Event name: blogPostView
Form Submission Trigger
- Type: Custom Event
- Event name: formSubmission
CTA Click Trigger
- Type: Custom Event
- Event name: ctaClick
Landing Page View Trigger
- Type: Page View
- Condition: Page Type equals landing-page
Essential Tags
Google Analytics 4 Configuration
- Tag type: GA4 Configuration
- Measurement ID: G-XXXXXXXXXX
- Trigger: All Pages
Blog Post View Event
- Tag type: GA4 Event
- Event name: view_item
- Parameters: Blog title, author, category
- Trigger: Blog Post View
Form Submission Event
- Tag type: GA4 Event
- Event name: generate_lead
- Parameters: Form ID, form type
- Trigger: Form Submission
CTA Click Event
- Tag type: GA4 Event
- Event name: select_promotion
- Parameters: CTA ID, CTA text
- Trigger: CTA Click
Variables Configuration
Create these variables for HubSpot data:
Data Layer Variables:
- DL - Portal ID
- DL - Page ID
- DL - Page Type
- DL - Form ID
- DL - Blog Author
- DL - Campaign
- DL - Source
- DL - Medium
Custom JavaScript Variables:
// Get HubSpot contact ID (if available) function() { var _hsq = window._hsq || []; return _hsq.push(['identify', {id: null}]); }URL Variables:
- UTM Parameters (campaign, source, medium, term, content)
Preview and Debug Mode
Using GTM Preview with HubSpot
- In GTM, click Preview button
- Enter your HubSpot page URL
- Click Connect
- Navigate through different HubSpot page types
- Verify data layer on each page type
- Test form submissions
- Test CTA clicks
Debugging with HubSpot Tools
Use HubSpot's debugging tools:
- Enable Preview mode in HubSpot
- Test pages before publishing
- Check browser console for errors
- Verify HubL variables render correctly
Common Debug Checks
- GTM container loads on all HubSpot domains
- Data layer includes portal and page information
- HubL variables render with actual values (not {{ }} syntax)
- Form submissions trigger correctly
- CTA clicks are captured
- Smart content variations tracked
- UTM parameters captured from URLs
- No conflicts with HubSpot's native tracking
Publishing Workflow
Pre-Publishing Checklist
- Test on all HubSpot page types (landing, blog, website, system)
- Verify form submission tracking on multiple forms
- Test CTA click tracking
- Check blog post data layer
- Verify UTM parameter capture
- Test on both published and preview URLs
- Validate in GTM preview mode
- Check for JavaScript console errors
Publishing Steps
- In GTM, click Submit
- Name version: "HubSpot Production v1.0"
- Add detailed description
- Click Publish
- Monitor HubSpot pages for issues
- Verify tracking in Google Analytics (if applicable)
HubSpot-Specific Considerations
- Test on both www and non-www versions
- Verify on custom domains
- Check subdomain implementations
- Test on HubSpot-hosted and external domains
Troubleshooting Common Issues
GTM Not Loading on HubSpot Pages
Symptoms: Container missing from page source
Solutions:
- Verify tracking code saved in HubSpot Settings
- Check if code applied to correct domain
- Ensure Marketing Hub tier supports custom tracking
- Clear HubSpot CDN cache
- Test in incognito mode
- Verify no ad blockers interfering
HubL Variables Not Rendering
Symptoms: Data layer shows {{ }} syntax literally
Solutions:
- Use correct HubL syntax for your version
- Verify variable exists in HubSpot context
- Check if variable available on page type
- Use
{{ variable|pprint }}for debugging - Review HubSpot documentation for variable names
- Test in Design Manager preview
Form Tracking Not Working
Symptoms: Form submissions not captured
Solutions:
- Verify form is HubSpot form (not external)
- Check message event listener code is correct
- Test with both embedded and popup forms
- Verify form ID in data layer matches
- Check for JavaScript errors in console
- Update to latest HubSpot form embed code
Data Layer Timing Issues
Symptoms: Data layer pushes after GTM loads
Solutions:
- Move data layer code before GTM container
- Use Head HTML section for critical data
- Ensure synchronous execution
- Avoid loading data layer in Footer HTML
- Check execution order in template
Template Updates Overwriting GTM
Symptoms: GTM removed after template updates
Solutions:
- Use Tracking Code method instead
- Document template customizations
- Create custom module for GTM
- Version control template changes
- Test after HubSpot updates
Multi-Domain Tracking Issues
Symptoms: Sessions breaking across domains
Solutions:
- Configure cross-domain tracking in GTM
- Add all HubSpot domains to linker list
- Include custom domains
- Test domain transitions
- Verify referral exclusions
Advanced Implementation
Progressive Profiling Tracking
Track progressive form fields:
<script>
window.addEventListener('message', function(event) {
if(event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormSubmit') {
dataLayer.push({
'event': 'progressiveForm',
'formFields': event.data.data.submissionValues,
'knownContact': event.data.data.isKnownContact
});
}
});
</script>
Lead Scoring Integration
<script>
{% if contact %}
dataLayer.push({
'contactScore': '{{ contact.hs_lead_score }}',
'lifecycleStage': '{{ contact.lifecyclestage }}',
'contactOwner': '{{ contact.hubspot_owner_id }}'
});
{% endif %}
</script>
Chatbot Interaction Tracking
<script>
window.hsConversationsOnReady = [function() {
window.HubSpotConversations.on('conversationStarted', function(payload) {
dataLayer.push({
'event': 'chatbotStarted',
'chatbotId': payload.conversation.id
});
});
}];
</script>
Meeting Scheduler Tracking
<script>
window.addEventListener('message', function(event) {
if(event.data.meetingBookSucceeded) {
dataLayer.push({
'event': 'meetingBooked',
'meetingType': event.data.meetingType
});
}
});
</script>
Workflow Enrollment Tracking
<script>
{% if contact.workflow_enrollment %}
dataLayer.push({
'event': 'workflowEnrollment',
'workflowId': '{{ contact.workflow_enrollment.workflow_id }}',
'enrollmentDate': '{{ contact.workflow_enrollment.enrolled_at }}'
});
{% endif %}
</script>
Performance Optimization
Async Loading
GTM loads asynchronously, but optimize further:
<script>
// Delay GTM for better initial page load
setTimeout(function() {
(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-XXXXXXX');
}, 2000);
</script>
Conditional Loading
Load GTM based on page type:
{% if content_type in ['landing-page', 'site-page'] %}
<!-- Load GTM -->
{% endif %}
Minimize Data Layer Payload
<script>
// Only include essential data
dataLayer.push({
'pageId': '{{ content.id }}',
'pageType': '{{ content_type }}'
// Omit large objects or arrays
});
</script>