Google Analytics Event Tracking in Drupal 7 (Hooks & OnClick)

Download's Drupal 7 module with all the files and sample code needed to get started! Check it out here.

Event tracking with Google Analytics has pretty much become a standard on all new sites. Google provides ample documentation on using the analytics, but the tricky part is getting it to integrate the way you want with Drupal. The Drupal Google Analytics module offers basic functionality out of the box. It can track page hits, specific user roles, offsite links, and download clicks. It also supports custom variables using tokens.

The Google Tag Manager is another amazing tool. You can set it up to trigger unique events based on various URLs in your site. This is useful for triggering events like "Contact Form Loaded," or "Form submitted successfully." However, it can't handle things like onClick events.

Triggering an event on a mouse click is easily implemented using JavaScript in a .js file. This method can be used to track user interactions like clicks, focus, and hovers that aren't handled by the Google Analytics Drupal module or the Google Tag Manager.

There are still occasions though, when none of the above methods are good enough. For example, you shouldn't track a form submit event on a submit click, because they might have filled it out incorrectly and the form will not actually be saved. The user will end up correcting the form and clicking submit again. That would cause two submission events to track when only one should count. If the form action goes directly to the same page, then the Tag Manager can't use the URL because it wouldn't know the difference between a form start and a form submission. The only way to get a reliable event is by hooking in to Drupal. If we trigger an event based off hook_comment_insert(), then the event will only trigger on a successful save and will not send a false positive if the validation failed. The catch with Drupal is that you can't output JavaScript code at that point in the Drupal process. Echoing raw JS or calling drupal_add_js() will not work in most hooks.

I have developed a Drupal module to provide a way to trigger Google track event calls using any Drupal hook. It stores all JavaScript files related to event tracking in the modules js folder. This means the theme layer remains clean and all the analytics events are in one easy to manage place. The code is actually quite simple, so I am just going to provide it here.

// Load tracking relevant to every page
function mymodule_page_alter(&$page) {
  drupal_add_js( drupal_get_path('module', 'mymodule') . '/js/global-events.js', array('type' => 'file', 'scope' => 'footer', 'group' => JS_THEME ));

  // Can also output inline JS - Example here outputting custom dataLayer variable
  global $user;
  foreach ($user->roles as $role) {
    drupal_add_js('dataLayer = [{\'memberType\': \''.$role.'\',}];', array('type' => 'inline', 'scope' => 'header', 'group' => JS_THEME ));

// Store the fact that a successful comment was saved
function mymodule_comment_presave($comment) {
  $_SESSION['comment_presave_hooked'] = true;

// Page preprocess function will add JS files for all hooks triggered in the $_SESSION
function mymodule_preprocess_page($variables) {
  if(isset($_SESSION['comment_presave_hooked'])) {
    drupal_add_js(drupal_get_path('module', 'mymodule') . '/js/comment_saved.js', array('type' => 'file', 'scope' => 'footer', 'group' => JS_THEME ));
  /* // A better solution psuedo-code
  * foreach ($_SESSION['triggered_hooks'] as &$hook_name) {
  *    drupal_add_js( '/js/' . $hook_name . '.js' );
  *  }
  *  unset ($_SESSION['triggered_hooks'];

The JavaScript file that is loaded globally for all pages will generally have functions attached to onClick events. The JavaScript files that are loaded on specific hooks, like user register and comment insert, will have functions that call a trackevent as soon as the page is loaded instead of waiting for a click.

The code should ideally be updated so the session carries an array of 'triggered_hooks', and in page preprocess, we could use one for loop to load a js file based on the name of the index. This way we wouldn't have to had an isset() check for each new hook we added.