Google Analytics 4 Setup on Drupal
Overview
This guide covers implementing Google Analytics 4 (GA4) on Drupal using both the official Google Analytics module and manual theme-level implementation. Learn how to properly configure GA4 while respecting Drupal's caching system and user privacy requirements.
Method 1: Using the Google Analytics Module (Recommended)
Installation
Via Composer (Recommended):
# Install the module
composer require drupal/google_analytics
# Enable the module
drush en google_analytics -y
# Clear cache
drush cr
Via Drupal UI:
- Download from https://www.drupal.org/project/google_analytics
- Upload to
/modules/contrib/ - Navigate to Extend (
/admin/modules) - Enable Google Analytics
Configuration
Navigate to Configuration → System → Google Analytics (/admin/config/system/google-analytics)
General Settings
Web Property ID:
G-XXXXXXXXXX
Enter your GA4 Measurement ID (starts with 'G-')
What are you tracking?
- ✅ A single domain (most common)
- ☐ One domain with multiple subdomains
- ☐ Multiple top-level domains
Pages to Track:
# Track all pages except admin
Pages: Leave blank (tracks all)
OR specify patterns:
# Only track specific paths
/blog/*
/products/*
/news/*
Pages to Exclude:
# Exclude admin pages (recommended)
/admin*
/user/*
/node/add*
/node/*/edit
/node/*/delete
User Tracking Settings
Track Users Based on Role:
☐ Track Administrator (not recommended for accurate data)
☑ Track Authenticated User
☑ Track Anonymous User
User Privacy:
☑ Anonymize IP addresses (GDPR compliance)
☑ Respect "Do Not Track" browser settings
☑ Enable Universal Analytics (if running dual tracking)
Advanced Settings
Track Files & Downloads:
☑ Track downloads
File extensions to track: pdf|docx|xlsx|zip|mp4|mp3
Track Mailto Links:
☑ Track mailto clicks
Track Outbound Links:
☑ Track clicks to external domains
Enhanced Link Attribution:
☑ Enable enhanced link attribution
Custom JavaScript Code:
// Add custom configuration
gtag('config', 'G-XXXXXXXXXX', {
'cookie_flags': 'SameSite=None;Secure',
'cookie_domain': 'auto',
'cookie_expires': 63072000, // 2 years
'allow_google_signals': true,
'allow_ad_personalization_signals': false
});
// Set custom dimensions
gtag('set', 'user_properties', {
'drupal_version': Drupal.settings.google_analytics.drupalVersion,
'user_role': Drupal.settings.google_analytics.userRole
});
Module Configuration via Code
For configuration management and deployments:
# config/sync/google_analytics.settings.yml
account: G-XXXXXXXXXX
cross_domains: ''
codesnippet:
create: []
before: ''
after: |
gtag('config', 'G-XXXXXXXXXX', {
'cookie_flags': 'SameSite=None;Secure',
'anonymize_ip': true
});
domain_mode: 0
track:
files: true
files_extensions: '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip'
mailto: true
linkid: true
urlfragments: false
userid: false
messages: { }
site_search: false
adsense: false
displayfeatures: false
privacy:
anonymizeip: true
donottrack: true
sendpageview: true
roles:
authenticated: authenticated
anonymous: anonymous
translation_set: false
cache: false
debug: false
visibility:
request_path_mode: '0'
request_path_pages: "/admin\r\n/admin/*\r\n/batch\r\n/node/add*\r\n/node/*/*\r\n/user/*/*"
user_role_mode: '0'
user_role_roles: { }
custom:
dimension: { }
metric: { }
Import configuration:
drush config:import -y
drush cr
Method 2: Manual Theme Implementation
For complete control, add GA4 directly to your theme.
Step 1: Create Library Definition
File: themes/custom/mytheme/mytheme.libraries.yml
google-analytics:
version: 1.x
js:
https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX:
type: external
attributes:
async: true
minified: true
js/google-analytics.js: {}
dependencies:
- core/drupal
- core/drupalSettings
Step 2: Create JavaScript File
File: themes/custom/mytheme/js/google-analytics.js
/**
* @file
* Google Analytics 4 tracking implementation.
*/
(function (Drupal, drupalSettings) {
'use strict';
// Initialize dataLayer
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// Basic configuration
gtag('js', new Date());
gtag('config', drupalSettings.googleAnalytics.trackingId, {
'cookie_flags': 'SameSite=None;Secure',
'anonymize_ip': true,
'cookie_domain': 'auto',
'send_page_view': true
});
// Track Drupal-specific data
if (drupalSettings.user) {
gtag('set', 'user_properties', {
'user_type': drupalSettings.user.uid > 0 ? 'authenticated' : 'anonymous',
'user_role': drupalSettings.googleAnalytics.userRole || 'anonymous'
});
}
// Track file downloads
Drupal.behaviors.googleAnalyticsTrackDownloads = {
attach: function (context, settings) {
var fileExtensions = settings.googleAnalytics.trackFileExtensions || 'pdf|docx|xlsx|zip';
var extensionPattern = new RegExp('\\.(' + fileExtensions + ')([\?#].*)?$', 'i');
// Use event delegation for better performance
document.addEventListener('click', function(event) {
var target = event.target.closest('a');
if (!target) return;
var href = target.href;
if (href && extensionPattern.test(href)) {
gtag('event', 'file_download', {
'file_extension': href.match(extensionPattern)[1],
'file_name': href.split('/').pop().split('?')[0],
'link_url': href,
'link_text': target.innerText || target.textContent
});
}
}, true);
}
};
// Track outbound links
Drupal.behaviors.googleAnalyticsTrackOutbound = {
attach: function (context, settings) {
var currentDomain = window.location.hostname;
document.addEventListener('click', function(event) {
var target = event.target.closest('a');
if (!target) return;
var href = target.href;
if (href && target.hostname !== currentDomain) {
gtag('event', 'click', {
'event_category': 'outbound',
'event_label': href,
'transport_type': 'beacon'
});
}
}, true);
}
};
// Track mailto links
Drupal.behaviors.googleAnalyticsTrackMailto = {
attach: function (context, settings) {
document.addEventListener('click', function(event) {
var target = event.target.closest('a');
if (!target) return;
var href = target.href;
if (href && href.indexOf('mailto:') === 0) {
var email = href.substring(7).split('?')[0];
gtag('event', 'mailto_click', {
'event_category': 'mailto',
'event_label': email,
'transport_type': 'beacon'
});
}
}, true);
}
};
})(Drupal, drupalSettings);
Step 3: Attach Library in Theme
File: themes/custom/mytheme/mytheme.theme
<?php
/**
* Implements hook_page_attachments().
*/
function mytheme_page_attachments(array &$attachments) {
// Don't track on admin pages
if (\Drupal::service('router.admin_context')->isAdminRoute()) {
return;
}
// Get configuration
$config = \Drupal::config('system.site');
$tracking_id = 'G-XXXXXXXXXX'; // Replace with your ID
// Attach library
$attachments['#attached']['library'][] = 'mytheme/google-analytics';
// Pass settings to JavaScript
$attachments['#attached']['drupalSettings']['googleAnalytics'] = [
'trackingId' => $tracking_id,
'trackFileExtensions' => 'pdf|docx|xlsx|zip|mp4|mp3',
'userRole' => _mytheme_get_user_primary_role(),
];
// Add cache context
$attachments['#cache']['contexts'][] = 'user.roles';
$attachments['#cache']['contexts'][] = 'url.path';
}
/**
* Get the primary role of the current user.
*/
function _mytheme_get_user_primary_role() {
$user = \Drupal::currentUser();
if ($user->isAnonymous()) {
return 'anonymous';
}
if ($user->hasPermission('administer site configuration')) {
return 'administrator';
}
$roles = $user->getRoles(TRUE); // Exclude authenticated role
return !empty($roles) ? reset($roles) : 'authenticated';
}
BigPipe Compatibility
Drupal's BigPipe module can affect GA4 loading. Ensure compatibility:
/**
* Implements hook_page_attachments().
*/
function mytheme_page_attachments(array &$attachments) {
// Add to every page, before BigPipe streams
$attachments['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#attributes' => [
'src' => 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX',
'async' => TRUE,
],
],
'google_analytics_script'
];
// Inline initialization (high priority)
$attachments['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => "window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','G-XXXXXXXXXX');",
],
'google_analytics_init'
];
}
GDPR Compliance
EU Cookie Compliance Integration
composer require drupal/eu_cookie_compliance
drush en eu_cookie_compliance -y
Configuration: /admin/config/system/eu-cookie-compliance
/**
* Implements hook_page_attachments().
*/
function mytheme_page_attachments(array &$attachments) {
// Check for analytics consent
$consent_service = \Drupal::service('eu_cookie_compliance.consent');
if ($consent_service && !$consent_service->hasConsent('analytics')) {
// Don't load analytics if no consent
return;
}
// Load analytics libraries
$attachments['#attached']['library'][] = 'mytheme/google-analytics';
}
JavaScript consent handling:
// Wait for consent before initializing
document.addEventListener('eu_cookie_compliance.changeStatus', function(event) {
if (event.detail.status === 'allow' && event.detail.categories.analytics) {
// User consented - load GA4
var script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
script.async = true;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX', {
'anonymize_ip': true
});
}
});
Testing Your Implementation
1. Verify Script Loading
# Check in browser DevTools
# Network tab -> Filter: google-analytics.com
# Should see: gtag/js and collect requests
2. Real-Time Reports
- Open GA4 → Reports → Realtime
- Visit your Drupal site
- Verify events appear in real-time
3. Debug Mode
// Enable debug mode in gtag config
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
Open browser console to see debug messages.
4. GA Debugger Extension
Install Google Analytics Debugger Chrome extension for detailed logging.
Common Issues & Solutions
Tracking Not Working
Clear Drupal cache:
drush crCheck module is enabled:
drush pm:list | grep google_analyticsVerify JavaScript aggregation:
- Disable temporarily to test:
/admin/config/development/performance - Clear all caches after changes
- Disable temporarily to test:
Admin Pages Being Tracked
Update exclusion patterns:
/admin*
/user/*
/node/add*
/node/*/edit
/taxonomy/term/*/edit
Duplicate Tracking
Check for multiple implementations:
- Module configuration
- Theme implementation
- GTM container also loading GA4
Performance Optimization
1. Use Async Loading
Already handled by both methods above.
2. Preconnect to GA Domains
$attachments['#attached']['html_head_link'][] = [
[
'rel' => 'preconnect',
'href' => 'https://www.google-analytics.com',
],
TRUE
];
$attachments['#attached']['html_head_link'][] = [
[
'rel' => 'dns-prefetch',
'href' => 'https://www.google-analytics.com',
],
TRUE
];
3. Delay for Non-Critical Interactions
// Load GA4 after page interactive
if ('requestIdleCallback' in window) {
requestIdleCallback(loadGoogleAnalytics);
} else {
setTimeout(loadGoogleAnalytics, 2000);
}