HubSpot GTM Integration | Blue Frog Docs

HubSpot GTM Integration

Integrate Google Tag Manager with HubSpot for centralized tag management.

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

HubSpot's tracking code settings provide the easiest implementation:

Site-Wide Installation:

  1. Log into your HubSpot account
  2. Navigate to Settings > Tracking & Analytics > Tracking Code
  3. Scroll to Add code for tracking & analytics section
  4. Click Head HTML tab
  5. 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 -->
  1. Click Footer HTML tab
  2. 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) -->
  1. 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:

  1. Navigate to Marketing > Files and Templates > Design Tools
  2. Open your template file (usually page.html or blog-post.html)
  3. 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 -->
  1. 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) -->
  1. 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:

  1. In Design Tools, create a new module
  2. Name it "Google Tag Manager"
  3. Add Custom HTML field
  4. Insert GTM code in the module
  5. Add module to header or footer template sections
  6. Configure module visibility if needed

Container ID Configuration

Finding Your Container ID

  1. Log into Google Tag Manager
  2. Navigate to your container
  3. Container ID displays in the format GTM-XXXXXXX
  4. 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:

  1. All Pages Trigger

    • Type: Page View
    • Fires on: All Pages
  2. Blog Post View Trigger

    • Type: Custom Event
    • Event name: blogPostView
  3. Form Submission Trigger

    • Type: Custom Event
    • Event name: formSubmission
  4. CTA Click Trigger

    • Type: Custom Event
    • Event name: ctaClick
  5. Landing Page View Trigger

    • Type: Page View
    • Condition: Page Type equals landing-page

Essential Tags

  1. Google Analytics 4 Configuration

    • Tag type: GA4 Configuration
    • Measurement ID: G-XXXXXXXXXX
    • Trigger: All Pages
  2. Blog Post View Event

    • Tag type: GA4 Event
    • Event name: view_item
    • Parameters: Blog title, author, category
    • Trigger: Blog Post View
  3. Form Submission Event

    • Tag type: GA4 Event
    • Event name: generate_lead
    • Parameters: Form ID, form type
    • Trigger: Form Submission
  4. 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:

  1. Data Layer Variables:

    • DL - Portal ID
    • DL - Page ID
    • DL - Page Type
    • DL - Form ID
    • DL - Blog Author
    • DL - Campaign
    • DL - Source
    • DL - Medium
  2. Custom JavaScript Variables:

    // Get HubSpot contact ID (if available)
    function() {
      var _hsq = window._hsq || [];
      return _hsq.push(['identify', {id: null}]);
    }
    
  3. URL Variables:

Preview and Debug Mode

Using GTM Preview with HubSpot

  1. In GTM, click Preview button
  2. Enter your HubSpot page URL
  3. Click Connect
  4. Navigate through different HubSpot page types
  5. Verify data layer on each page type
  6. Test form submissions
  7. Test CTA clicks

Debugging with HubSpot Tools

Use HubSpot's debugging tools:

  1. Enable Preview mode in HubSpot
  2. Test pages before publishing
  3. Check browser console for errors
  4. 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

  1. In GTM, click Submit
  2. Name version: "HubSpot Production v1.0"
  3. Add detailed description
  4. Click Publish
  5. Monitor HubSpot pages for issues
  6. 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>
// SYS.FOOTER