Drupal GTM Integration
Complete guide to setting up Google Tag Manager (GTM) on your Drupal site for centralized tracking and tag management.
Getting Started
GTM Setup Guide
Step-by-step instructions for installing GTM on Drupal.
Data Layer Implementation
Implement a comprehensive data layer for enhanced tracking capabilities.
Why Use GTM with Drupal?
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 Drupal:
- Drupal 9 or Drupal 10 installation (or Drupal 7 for legacy sites)
- Administrator access to Drupal admin panel
- Google Tag Manager account created
- GTM Container ID (format: GTM-XXXXXXX)
- Composer access for module installation (recommended)
- Understanding of Drupal's module system
Installation Methods
Method 1: Google Tag Manager Module (Recommended)
The official Drupal GTM module provides the most robust implementation:
Installation via Composer:
composer require drupal/google_tag
drush en google_tag
Configuration Steps:
- Navigate to Configuration > System > Google Tag Manager
- Click Add Container
- Configure container settings:
- Label: Production Container
- Container ID: GTM-XXXXXXX
- Insert snippet on: All pages
- Weight: -100 (load early)
- Configure insertion conditions:
- Path patterns: Leave empty for all pages
- Role conditions: Configure user role visibility if needed
- Click Save
Module Features:
- Multiple container support
- Role-based insertion
- Path-based conditions
- Data layer integration
- Automatic noscript tag insertion
- Cache management
Method 2: Theme Template Integration
For custom theme implementations:
- Edit your theme's
html.html.twigtemplate - Add GTM head code after opening
<head>tag:
{% if google_tag_manager_id %}
<!-- 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','{{ google_tag_manager_id }}');</script>
<!-- End Google Tag Manager -->
{% endif %}
- Add noscript code after opening
<body>tag:
{% if google_tag_manager_id %}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id={{ google_tag_manager_id }}"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{% endif %}
- Set container ID in theme settings or configuration
Method 3: Custom Module Development
For advanced control, create a custom module:
yourmodule.info.yml:
name: Custom GTM
type: module
description: 'Custom Google Tag Manager implementation'
core_version_requirement: ^9 || ^10
dependencies:
- drupal:node
yourmodule.module:
<?php
/**
* Implements hook_page_attachments().
*/
function yourmodule_page_attachments(array &$attachments) {
$config = \Drupal::config('yourmodule.settings');
$container_id = $config->get('gtm_container_id');
if (!empty($container_id)) {
// Add GTM script to head
$attachments['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => "(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','{$container_id}');",
],
'google_tag_manager',
];
}
}
Container ID Configuration
Managing Container IDs
The Google Tag Manager module supports multiple containers:
- Navigate to Configuration > System > Google Tag Manager
- Click Add Container for each environment
- Configure container conditions:
- Production: Default container for all users
- Development: Conditional on specific domain or IP
- Staging: Path-based or role-based insertion
Environment-Specific Containers
// In settings.php
if (getenv('ENVIRONMENT') === 'production') {
$config['google_tag.container.default']['container_id'] = 'GTM-PROD123';
} else {
$config['google_tag.container.default']['container_id'] = 'GTM-DEV456';
}
Data Layer Implementation
Drupal Data Layer Module
Install the Data Layer module for structured data:
composer require drupal/datalayer
drush en datalayer
Configuration:
- Navigate to Configuration > Search and metadata > Data Layer
- Enable desired data layer variables:
- Entity type (node, user, taxonomy term)
- Entity bundle (article, page, product)
- User information
- Language
- Page title
- Configure output method (globally or per entity)
Basic Data Layer Structure
<script>
dataLayer = [{
'drupalLanguage': 'en',
'drupalCountry': 'US',
'entityType': 'node',
'entityBundle': 'article',
'entityId': '123',
'entityTitle': 'Article Title',
'entityCreated': '2024-01-15',
'userStatus': 'authenticated',
'userUid': '456'
}];
</script>
Content Type Specific Data Layer
For article nodes:
/**
* Implements hook_page_attachments().
*/
function mymodule_page_attachments(array &$page) {
$node = \Drupal::routeMatch()->getParameter('node');
if ($node && $node->bundle() === 'article') {
$data = [
'contentType' => 'article',
'contentId' => $node->id(),
'contentTitle' => $node->getTitle(),
'contentAuthor' => $node->getOwner()->getDisplayName(),
'contentCategory' => $node->get('field_category')->entity->getName(),
'contentTags' => array_map(function($term) {
return $term->entity->getName();
}, $node->get('field_tags')->getValue()),
];
$page['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => 'dataLayer.push(' . json_encode($data) . ');',
],
'datalayer_article',
];
}
}
Commerce Data Layer (Drupal Commerce)
For ecommerce tracking with Drupal Commerce:
/**
* Product view data layer.
*/
function mymodule_commerce_product_view(array &$page) {
$product = \Drupal::routeMatch()->getParameter('commerce_product');
if ($product) {
$variation = $product->getDefaultVariation();
$data = [
'event' => 'productDetail',
'ecommerce' => [
'detail' => [
'products' => [[
'name' => $product->getTitle(),
'id' => $product->id(),
'price' => $variation->getPrice()->getNumber(),
'category' => $product->get('field_category')->entity->getName(),
]],
],
],
];
$page['#attached']['drupalSettings']['gtm']['productData'] = $data;
}
}
Form Submission Tracking
// Add to custom JavaScript file
(function ($, Drupal, drupalSettings) {
'use strict';
Drupal.behaviors.gtmFormTracking = {
attach: function (context, settings) {
$('form', context).once('gtm-form').on('submit', function() {
dataLayer.push({
'event': 'formSubmission',
'formId': $(this).attr('id'),
'formAction': $(this).attr('action')
});
});
}
};
})(jQuery, Drupal, drupalSettings);
Common Triggers and Tags
Essential Triggers for Drupal
Create these triggers in GTM:
All Pages Trigger
- Type: Page View
- Fires on: All Pages
Content View Trigger
- Type: Custom Event
- Event name: contentView
- Condition: Entity Type equals node
Form Submission Trigger
- Type: Custom Event
- Event name: formSubmission
User Registration Trigger
- Type: Custom Event
- Event name: userRegistration
Commerce Purchase Trigger (if using Drupal Commerce)
- Type: Custom Event
- Event name: purchase
Essential Tags
Google Analytics 4 Configuration
- Tag type: GA4 Configuration
- Measurement ID: G-XXXXXXXXXX
- Trigger: All Pages
Content View Event
- Tag type: GA4 Event
- Event name: view_item
- Parameters: Entity type, bundle, ID
- Trigger: Content View
Form Submission Event
- Tag type: GA4 Event
- Event name: form_submit
- Parameters: Form ID, form action
- Trigger: Form Submission
Variables Configuration
Create these variables for Drupal-specific data:
Data Layer Variables:
- DL - Entity Type
- DL - Entity Bundle
- DL - Entity ID
- DL - User Status
- DL - Language Code
Custom JavaScript Variables:
function() { return drupalSettings.path.currentPath; }DOM Variables:
- Page Title:
{{Page Title}} - Content Author: CSS Selector for author field
- Page Title:
Preview and Debug Mode
Using GTM Preview with Drupal
- In GTM, click Preview button
- Enter your Drupal site URL
- Click Connect
- Navigate through different content types
- Verify data layer variables populate correctly
Debugging Data Layer
Use browser console to inspect data layer:
// View entire data layer
console.log(dataLayer);
// Watch for new pushes
var originalPush = dataLayer.push;
dataLayer.push = function() {
console.log('Data Layer Push:', arguments);
return originalPush.apply(this, arguments);
};
Drupal Debugging Tools
Enable development mode for debugging:
// In settings.local.php
$config['google_tag.container.default']['debug_output'] = TRUE;
Common Debug Checks
- GTM container loads on all page types
- Data layer includes entity information
- User authentication status tracked correctly
- Form IDs captured accurately
- Commerce product data structured properly
- No PHP errors in Drupal logs
- Cache cleared after configuration changes
Publishing Workflow
Pre-Publishing Checklist
- Test GTM on all content types (article, page, etc.)
- Verify data layer on authenticated and anonymous users
- Test form submissions
- Check commerce events (if applicable)
- Test on mobile and desktop
- Clear Drupal cache
- Review GTM preview summary
- Validate no JavaScript console errors
Publishing Steps
- In GTM, click Submit
- Name version: "Drupal Production v1.0"
- Describe changes in detail
- Click Publish
- Monitor Drupal logs for issues
- Clear Drupal cache after publishing
Cache Management
Clear Drupal cache after GTM changes:
drush cache-rebuild
Or via admin UI: Configuration > Development > Performance > Clear all caches
Troubleshooting Common Issues
GTM Container Not Loading
Symptoms: Container code missing from page source
Solutions:
- Verify Google Tag module is enabled:
drush pm:list --status=enabled | grep google_tag - Check container configuration at
/admin/config/system/google_tag - Clear Drupal cache:
drush cr - Verify module weight (should load early)
- Check path conditions aren't blocking insertion
- Review role-based visibility settings
Data Layer Variables Empty
Symptoms: Data layer shows but variables are undefined
Solutions:
- Verify Data Layer module is installed and enabled
- Check entity has required fields populated
- Clear render cache:
drush cache:rebuild-all - Verify field machine names match data layer configuration
- Check entity is loaded in current route context
- Review PHP error logs:
drush watchdog:show
Module Conflicts
Symptoms: GTM not working with other modules
Solutions:
- Check for JavaScript aggregation issues
- Disable JavaScript optimization temporarily
- Review module load order
- Check for conflicting page_attachments implementations
- Disable other analytics modules temporarily
- Review Recent log messages:
/admin/reports/dblog
Cache Issues
Symptoms: GTM changes not appearing after updates
Solutions:
- Clear all Drupal caches:
drush cr - Clear browser cache
- Disable page cache for testing
- Check cache tags on container configuration
- Verify cache contexts
- Use Cache Metadata module for debugging
Template Override Issues
Symptoms: GTM code removed after theme updates
Solutions:
- Use module-based implementation instead
- Create custom sub-theme
- Document theme modifications
- Use hook implementations instead of template edits
- Version control theme files
- Test after theme updates
Permissions Issues
Symptoms: GTM only appears for certain users
Solutions:
- Review role-based visibility settings
- Check user permissions for GTM module
- Verify container conditions
- Test with different user roles
- Review access control settings
- Check for user-based caching issues
Advanced Implementation
Multi-Site GTM Configuration
For Drupal multi-site installations:
// In sites/site1/settings.php
$config['google_tag.container.default']['container_id'] = 'GTM-SITE1';
// In sites/site2/settings.php
$config['google_tag.container.default']['container_id'] = 'GTM-SITE2';
Custom Entity Type Tracking
/**
* Track custom entity views.
*/
function mymodule_entity_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
if ($entity->getEntityTypeId() === 'custom_entity' && $view_mode === 'full') {
$build['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => 'dataLayer.push({
"event": "customEntityView",
"entityId": "' . $entity->id() . '",
"entityLabel": "' . $entity->label() . '"
});',
],
'custom_entity_tracking',
];
}
}
Webform Integration
Track Drupal Webform submissions:
/**
* Implements hook_webform_submission_insert().
*/
function mymodule_webform_submission_insert(WebformSubmissionInterface $submission) {
$webform = $submission->getWebform();
$data = [
'event' => 'webformSubmission',
'formId' => $webform->id(),
'formTitle' => $webform->label(),
];
// Attach to next page load
\Drupal::service('session')->set('gtm_webform_data', $data);
}
Commerce Enhanced Ecommerce
Full Drupal Commerce integration:
/**
* Add to cart event.
*/
function mymodule_commerce_cart_entity_add(CartInterface $cart, OrderItemInterface $order_item, $quantity) {
$purchased_entity = $order_item->getPurchasedEntity();
$data = [
'event' => 'addToCart',
'ecommerce' => [
'currencyCode' => $order_item->getTotalPrice()->getCurrencyCode(),
'add' => [
'products' => [[
'name' => $purchased_entity->label(),
'id' => $purchased_entity->id(),
'price' => $order_item->getUnitPrice()->getNumber(),
'quantity' => $quantity,
]],
],
],
];
\Drupal::service('session')->set('gtm_add_to_cart', $data);
}
Performance Optimization
Async Loading
The Google Tag module loads GTM asynchronously by default. Verify in configuration:
Configuration > System > Google Tag Manager > Advanced > Include script as fallback
Conditional Loading
Load GTM only where needed:
// In custom module
function mymodule_page_attachments_alter(array &$attachments) {
$route_name = \Drupal::routeMatch()->getRouteName();
// Don't load GTM on admin pages
if (strpos($route_name, 'admin') === 0) {
foreach ($attachments['#attached']['html_head'] as $key => $value) {
if (isset($value[1]) && $value[1] === 'google_tag_manager') {
unset($attachments['#attached']['html_head'][$key]);
}
}
}
}
Cache Optimization
Configure appropriate cache tags:
$build['#cache']['tags'][] = 'google_tag_container';
$build['#cache']['contexts'][] = 'user.roles';