How to use Pantheon stripped “utm_*” query string parameters in Drupal

 

Pantheon strips the values of all query string parameters starting with "utm_" and replaces these with "PANTHEON_STRIPPED". Although they have good reasons to do this (performance), this practice does prevent you from using the values in php. If you want to store the "utm_*" values for use within your Drupal application, you will have to use javascript to do so. In this article we show you how to do just that.

Published on February 21, 2025

Stripping what?

For those using Pantheon as a hosting provider, it is good to know that url’s containing query string parameters starting with “utm_” will have the value of that parameter automatically replaced with “PANTHEON_STRIPPED”. Utm query string parameters are used to create urls (use Google URL builder) that can be used in marketing campaigns working with Google Analytics. Pantheon explains (here and here) that this is done to improve the performance of their edge cache solution. Ignoring all “utm_” values in url’s allows for far fewer urls to be keyed as unique, allowing for higher cache hit ratios. This all makes total sense of course.

Why do I care?

A problem arises though when you would like to use the values of these “utm_*” query string parameters in your Drupal application. You could, for instance, want to store the values with each webform submission and allow a member of the marketing department to filter submissions by one of the utm values. Storing the value this way would result in “PANTHEON_STRIPPED” being stored in the database. As Pantheon explains in their own words: “Variables that are converted to PANTHEON_STRIPPED cannot be read with PHP, and original values will not appear in the nginx-access.log or be available to the application backend.”

How do we fix this?

Because it is clear that this cannot be resolved using php (as stated above, the values as stripped before they reach the php container), we use javascript to solve the problem. We use the (fantastic) webform module to make forms and add a hidden field for each “utm_*” parameter that we want to store the values for, see the screenshot below.

Example of Drupal webform with hidden utm fields

In order to pre-fill these webform fields, a small javascript library is created that is attached to each webform we want to use it for. The contents of the javascript library is shown below, this file is placed as crmleads.js in the Drupal application (in a custom module).

(function ($, Drupal, drupalSettings) {
  Drupal.behaviors.crmleads = {
    attach(context, settings) {
      const urlParams = new URLSearchParams(window.location.search);
      const utmFields = ["utm_source", "utm_medium", "utm_campaign"];

      utmFields.forEach(field => {
        const value = urlParams.get(field) || "";
        const sanitizedValue = this.sanitizeInput(value);
        $(`[name="${field}"]`, context).val(sanitizedValue);
      });
    },

    // Helper method to sanitize input and prevent XSS
    sanitizeInput(input) {
      const temp = document.createElement("div");
      temp.textContent = input; // textContent is safer and faster than innerHTML for escaping
      return temp.innerHTML;
    }
  };
})(jQuery, Drupal, drupalSettings);

While I won't go through the code line by line, it might be good to note the array "utmFields" where we define which "utm_*" parameters to look for (in this case the most common ones: utm_source, utm_medium and utm_campaign). A helper method "sanatizeInput" is also included to do some basic XSS prevention.

In order to get this library loaded, you will need to define the code above to be a library in Drupal (see here for more info on working with libraries in Drupal). See what this looks like below, place this code in crmleads.libraries.yml.

crmleadsjs:
  version: 1.x
  js:
    js/crmleads.js: {}

Finally, the conditional loading of the javascript code can be done by implementing a hook function. The code below shows what this could look like, place this code in a .module file that is part of your custom module.

/**
 * Implements hook_webform_submission_form_alter().
 */
function crmleads_webform_submission_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $is_crm_webform = \Drupal::service('crmleads.lead_webform_categories')->isCrmWebform($form['#webform_id']);
  if ($is_crm_webform) {
    // Attach library to include custom behavior (JS).
    $form['#attached']['library'][] = 'crmleads/crmleadsjs';
  }
}

In the code above a custom service is used in order to determine if the webform is marked a CRM form and therefore should have the javascript library loaded. This logic is specific to our clients needs and could be solved in a different way. The main takeaway here is that you can load the javascript code based on you own business logic. As long as the javascript library is loaded alongside a webform that uses the 3 hidden utm_* fields, these fields will be pre-filled with the values found in the url and stored in the submission.

About the author

Marco Bouwer (drupal.org) started his interest in computers with gaming on the Nintendo Entertainment System (NES) and programming on the Amiga 500 (Commodore). A Fulbright scholarship brought him a year at Augsburg College (Minneapolis) where a genuine interest in the internet was sparked. After working with technologies like ColdFusion, advising clients at Price Waterhouse Coopers and having build a custom Content Management System (CMS), he co-founded The Savvy Few in 2011. When not on the water kiteboarding, wave-kiting, windsurfing or wing-foiling, he is probably working a some Drupal related challenge. He enjoys being actively involved in the Drupal open-source community and travels all over the world attending Drupal related meetups.

drupal
drupal 10
development
planet drupal
javascript
Digital marketing
Drupal 11