Google Analytics 4 Setup on Drupal | Blue Frog Docs

Google Analytics 4 Setup on Drupal

Complete guide to implementing GA4 on Drupal using modules and manual methods

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.


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:

  1. Download from https://www.drupal.org/project/google_analytics
  2. Upload to /modules/contrib/
  3. Navigate to Extend (/admin/modules)
  4. 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

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

  1. Open GA4 → Reports → Realtime
  2. Visit your Drupal site
  3. 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

  1. Clear Drupal cache:

    drush cr
    
  2. Check module is enabled:

    drush pm:list | grep google_analytics
    
  3. Verify JavaScript aggregation:

    • Disable temporarily to test: /admin/config/development/performance
    • Clear all caches after changes

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);
}

Next Steps

// SYS.FOOTER