Support Google Analytics 4/gtag.js

Created on 30 September 2020, over 3 years ago
Updated 12 June 2023, about 1 year ago

Problem/Motivation

Currently gtag.js, which is recommended by Google, is not supported by the 7.x version of the module. It uses analytics.js. gtag.js is also required for Google Analytics 4, which is the successor to Universal Analytics. UA is being deprecated in favor of GA4. Google is actively pushing site owners to GA 4 and UA will stop receiving data in july 2023.

Steps to reproduce

Install 7.x later released version and notice it uses analytics.js and not gtag.js

Proposed resolution

Support gtag.js

Feature request
Status

Fixed

Version

2.0

Component

Code

Created by

🇺🇸United States ajayg

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • Issue was unassigned.
  • Status changed to Needs review over 1 year ago
  • First commit to issue fork.
  • 🇺🇸United States japerry KVUO

    I just merged in David and Matt's patch together into Matt's PR. Hopefully some people can review that, and if we're good I can get this committed!

  • 🇺🇸United States mglaman WI, USA

    I re-tested, all works. And now links have been updated as well. +1 from this side.

  • 🇸🇰Slovakia poker10

    Thanks for the work here! I have noticed a small issue with a commented line left in the MR (added a comment there), but not changing the status as it is only a minor problem.

  • Status changed to Fixed over 1 year ago
  • 🇺🇸United States japerry KVUO

    Thanks everyone for their work and reviews on this issue! its been committed. If possible, it'd be nice to get some testing with the merged PR on 7.x-2.x-dev before making a release. But regardless, I plan on making a new 7.x-2.x release no later than next Wednesday, Feb 1st.

  • Status changed to Needs work over 1 year ago
  • 🇺🇸United States vinmassaro

    I'm testing out 7.x-2.x-dev and when using a G- code, the site loads fine. When using both a G- code and a UA- code, or just a UA-code by itself, I can't even load the site because the page just hangs on load and Firefox says "This page is slowing down Firefox. To speed up your browser, stop this page". If I click to debug the script, the console is showing an overload of requests to googletagmanager.com with 1UA. appended over and over to the &epr parameter. Please see the attached screenshot. Thanks!

  • 🇳🇿New Zealand xurizaemon Ōtepoti, Aotearoa 🏝

    @vinmassaro are you able to reproduce that on another site? I'm not seeing that behaviour myself, and mindful there are other vectors which might factor into it happening for you, which could include:

    • Browser configuration
    • Browser plugins & configuration
    • Module configuration
    • Configuration of custom metrics & dimensions (perhaps?)
    • Integration & configuration of modules which modify behaviour of Google Analytics module (eg perhaps Commerce Google Analytics, GA Push)

    That list above isn't exhaustive or particularly well informed, just some starter items you could consider when investigating.

    With only the information in #3174214-102: Support Google Analytics 4/gtag.js I don't have context to debug your situation - noting that I'm not observing it in my testing, and that there are other factors which might be in play for you. Would be a great contribution if you or others are able to track it down so this issue can progress!

    Thanks all for the work in this issue :)

  • 🇺🇸United States mglaman WI, USA

    I'm very confused because the URL is https://www.googletagmanager.com/a?id but nothing of that sort is in this module's code base. Are there other Google Analytic contrib installed?

  • Status changed to Fixed over 1 year ago
  • 🇺🇸United States japerry KVUO

    Thanks for the report vinmassaro ! for tracking purposes, can you make a new issue for this? Also want to verify if its still causing an issue. The output I'm seeing from your screenshot appears a bit odd since the URL doesn't match what is in the code: https://www.googletagmanager.com/gtag/js?id= vs https://www.googletagmanager.com/a?id=

    re-Marking fixed again.

  • 🇺🇸United States joshuautley

    As an observer, this is why I continue to push for our clients to adopt Drupal. The community of developers (and designers) are absolute rock stars. Here we are with module development going strong for version seven while version ten was just released. The continued support in our community is impressive. If any of you are ever in San Diego, reach out, and let's connect.

  • Status changed to Needs review over 1 year ago
  • 🇺🇸United States vinmassaro

    Sorry, I'm changing this back to "Needs review" because I reproduced the issue on two clean Drupal 7.94 sites with only 7.x-2.x-dev installed. I don't think it should be in a separate issue because that assumes this issue is fixed and ready for a new stable release. If you disagree, I'm happy to open a separate issue after you can review the behavior. I spun up a new site on Pantheon and verified it again in a separate site on simplytest.me. Please see here: http://dev-ga4-3174214.pantheonsite.io

    Tested in Firefox, Chrome, and Safari on MacOS Ventura 13.1. Steps to reproduce:

    1. Create a new Drupal 7.94 site
    2. Install Google Analytics 7.x-2.x-dev (7.x-2.6+3-dev)
    3. On the Google Analytics module configuration page, enter a valid UA- code
    4. With ad blocking browser extensions off, visit the home page of your site and see that it does not fully load and causes the browser tab to hang and either be stopped or debugged. You can also toggle ad blocking plugins on/off to see the behavior change.

    Thanks!

  • 🇺🇸United States torgosPizza Portland, OR

    For what it's worth I am also able to reproduce this behavior with the latest updates from -dev. The behavior goes away if I revert back. We don't have Google Tag Manager installed but we do use a few other JS modules like Stripe. The problem becomes apparent when the browser stalls at the TLS handshake phase. Will be digging in to see if I can isolate the cause.

  • 🇺🇸United States torgosPizza Portland, OR

    It appears that removing the gtag-shim.sj file resolves it for me. I tested by simply commenting out this line:

    @@ -279,7 +284,7 @@ function googleanalytics_page_alter(&$page) {
         // Build tracker code.
         $script = 'window.dataLayer = window.dataLayer || [];';
         $script .= 'function gtag(){dataLayer.push(arguments)};';
    -    $script .= PHP_EOL . file_get_contents(__DIR__ . '/googleanalytics.gtag-shim.js') . PHP_EOL;
    +//    $script .= PHP_EOL . file_get_contents(__DIR__ . '/googleanalytics.gtag-shim.js') . PHP_EOL;
         $script .= 'gtag("js", new Date());';
         // Developer ID for Drupal provided by Google.
         $script .= 'gtag("set", "developer_id.dMDhkMT", true);';
    

    It requires to clear "all cache" for the change to get picked up with anonymous visitors due to Drupal page caching. Hopefully this can help us narrow down the root cause and a better fix than removing the shim (which I think would likely be helpful to others).

  • 🇺🇸United States mglaman WI, USA
    -if (typeof ga !== 'function') {
    +if (typeof ga === undefined) {
    

    Oh bummer. That means something else is defining it, and it's not a function but something else?

    @torgosPizza can you console.debug(ga) and see what its returning?

  • 🇺🇸United States torgosPizza Portland, OR

    It says it is a function that's being defined in analytics.js.. The log output shows up as ƒ (a){J(1);Z.D.apply(Z,[arguments])}

  • 🇺🇸United States mglaman WI, USA

    How is https://www.google-analytics.com/analytics.js still loading on the page? This shouldn't be adding that anymore.

  • 🇺🇸United States torgosPizza Portland, OR

    From what I can tell in my network calls, the analytics.js call is being requested by the file https://www.googletagmanager.com/gtag/js.

  • 🇺🇸United States Electric Doorknob

    I've been working with @torgosPizza on this. Based on research and testing, I can confirm that gtag does include and invoke analytics.js. This can be demonstrated using this basic test page

    <html>
      <head>
        <!-- Google tag (gtag.js) -->
        <script async src="https://www.googletagmanager.com/gtag/js?id=REDACTED"></script>
        <script>
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
    
          gtag('config', 'REDACTED');
        </script>
      </head>
      <body>
        gtag.js test
      </body>
    </html>
    

    and noting that window.ga is defined.

    Gtag provides analytics.js for backward compatibility, and I believe it currently uses it internally. This is why overriding window.ga() with new function that calls gtag() can cause the recursive behavior being seen. I suspect Google may change that in July when Universal Analytics is deprecated at which point the shim may be needed to retain compatibility with callers of ga().

    Note: UBlock Origin substitutes its own code for gtag accounting for the difference in behavior when that ad blocker is enabled.

    @mglaman: a note on your shim as described above. To provide a shim for ga() in case it is not defined, I'd use a construction like this:

    window.ga = window.ga || function() {
      ...
    }
    

    Block level function declarations are weird and inconsistent: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statem...

  • 🇺🇸United States japerry KVUO

    Confirmed with Google that yes, if you use a UA- property, the gtag.js system will include analytics.js. Since Matt only has a G- property (GA4), analytics.js doesn't get included. We can make some tweaks to the shim itself, but could also detect what property you're using from PHP as well.

  • Status changed to Needs work over 1 year ago
  • 🇺🇸United States torgosPizza Portland, OR

    I thought it might have something to do with migrating a UA property to GA4. Google recommends that practice, and for us it's a requirement because we're maintaining our historical data. I think we should set this to Needs Work until there is a way to detect this and only serve the shim as necessary as per @japerry in #115.

  • Status changed to Needs review over 1 year ago
  • 🇺🇸United States torgosPizza Portland, OR

    Here's a very simple patch that uses strpos() to detect whether the ID does not start with UA-, and if that is the case, includes the shim in the script. I'm open to a better solution, just not sure on details regarding the different IDs.

  • 🇺🇸United States mglaman WI, USA

    I think #117 is the fix, it's the tried and trusted equivalent to str_starts_with in PHP 8.

    Sorry folks, I tested with G- tracking IDs because I didn't have UA- ones and I didn't think of the fact they'd load it in. I think this is the right fix. But I am also wondering if we should put loading the shim behind a configuration flag as well. That way it's easier to say "nope, do not do this."

  • 🇺🇸United States torgosPizza Portland, OR

    It happens! :)

    But I am also wondering if we should put loading the shim behind a configuration flag

    That's not a bad idea, however I think we'd want to be very explicit about the side effect, e.g. "Your pages may become unresponsive in some browsers due to infinite recursion." Hopefully that'd be enough to ward off anyone turning it on willy-nilly, and then only put it behind a variable like google_analytics_include_shim that overrides the default behavior when TRUE - rather than in the configuration form. The reason being: unless they have /admin pages set to not load the GA script, they may not be able to turn it off quickly if they enable the shim and the page hangs. I'd be concerned if someone doesn't know how to set a variable through Drush in that case.

  • 🇺🇸United States vinmassaro

    Thanks for the updates here. Can you outline what the behavior is for the shim, when/why it is needed, etc?

  • 🇺🇸United States torgosPizza Portland, OR

    A shim is a backwards-compatible file that usually includes the old functions that otherwise would become unavailable after an upgrade.

    The shim included by the patch in this issue provides essentially a translation for the new GA4 function gtag() that accepts calls to the previous function, ga().

    So in other words, if you upgrade from the version of Google Analytics that uses gtag(), but some javascript in your site still makes calls to the previous function ga(), you won't run into errors because the shim has provided a new method called ga() which allows you to upgrade smoothly.

  • Status changed to Needs work over 1 year ago
  • 🇺🇸United States jenlampton

    I'm having trouble with both the 7.x-2.x-dev release on it's own, and while using the patch from #117.

    Without the patch, my home page hangs as described in #107 and #108.

    With the patch, I don't get any data at all for my UA property, and I get only partial data for the GA4 property (which might just be how GA4 works -- it's been only 3 days and perhaps it takes longer than that to show any accurate data).

    I have entered both codes UA- and G- because I am hoping to track data in both places until we can get our GA4 working properly. I don't think the solution in #117 accounts for sites that are using more than one analytics ID.

  • 🇺🇸United States torgosPizza Portland, OR

    @jenlampton: Thanks for the insight, I hadn't tested with multiple IDs but can confirm the same issue you're seeing. I wrote a fix to the patch that I'll upload, it basically loops through each $id_list value and checks for non "UA-" values, adding the shim only once at that point.

    However I'm still getting an infinite loop, the problem is still in the "create" call. ga.create() calls gtag.config() which seems to call ga.create() again ad infinitum.

    We might just not need the shim at all, since Google seems intelligent enough to include the ga() functions if the property being configured needs it. I commented out the shim and tested with both a G- and a UA- property and it seems to work as expected without requiring the shim. When a "UA-" property ID is present, analytics.js pulls in its own version of ga(). In that case, the function ga() is still defined.

    So in my opinion the best approach will be to simply omit the shim altogether. If the project owners are okay with this I can spin up a patch that removes the file and the line of code that loads it. Unless there is a reason that we should include it that I haven't thought of, but anyone using this module will fall under 1 of 2 scenarios:

    1) Only using GA4 properties in which case they should never invoke ga() directly;
    2) Migrating from UA to GA4, in which case ga() is already defined automatically.

  • 🇺🇸United States japerry KVUO

    Using both tags is common and something google recommends. The shim is only needed post UA EOL so other Drupal or site JS that calls the old ga functions continues to work.

    At first glance, I thought too we only needed to add the shim if you have only G- properties. However, thats not entirely true either! See this correspondence from google:

    If a G- tag is imported is imported like this:

    <script async 
    src="https://www.googletagmanager.com/gtag/js?id=G-ABCD1234"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
     
      gtag('config', 'G-ABCD1234');
    </script>
    

    Then it will usually only load one library: https://www.googletagmanager.com/gtag/js?id=TAG_ID

    There are cases where configuring gtag.js like in the snippet above will also load analytics.js or other Google product libraries that are associated with the tag. For example, if I connect a UA property to this GA4 property using Connected Site Tags, it will load the analytics.js (and ec.js) libraries as well. Any additional libraries required are included in the dom just above the gtag snippet where it was configured.

    It seems like between now and 2024, we probably need a checkbox to enable the shim if developers are seeing javascript errors because ga.send is missing. After analytics.js is actually sunset (in 2024 with 360 properties) then it should be safe to include the shim.

  • Status changed to Needs review over 1 year ago
  • 🇺🇸United States torgosPizza Portland, OR

    Got it, thanks @japerry - this is a good point.

    Attached is a new patch that creates a new checkbox in the admin form and only includes the shim if it's been enabled.

    It also updates the shim JS file to use the window value rather than a pure function block as described in #114.

    Please test. Thanks!

  • 🇺🇸United States vinmassaro

    @torgosPizza thanks for the updated patch. I will test and report back.

  • 🇺🇸United States jenlampton

    I've also been testing the latest patch, but even though I can see both tracking codes appearing in the source code on my site, I am not seeing any data flowing into either our GA4 property, or our old UA property via the "Realtime" data measurements. I'm seeing nothing in the last 5 minutes on either, regardless of all my activity on the site in Chrome, Firefox, and Safari as an anonymous user.

    I've also tried removing the new GA4 code to see if that would fix the tacking for the old UA property, but it does not.

    I'm going to revert to the latest release of the module to see if that fixes UA, or if maybe there's just something wrong with my activity or my properties.

  • 🇺🇸United States jenlampton

    Update: I have confirmed that data started flowing into my old UA property as soon as I reverted to the 7.x-2.6 version of the module, so there is definitely something going wrong with how things are set up in -dev.

    I think this is how the gtag.js script is added to the page:

    <script type="text/javascript" src="https://www.googletagmanager.com/gtag/js?id=UA-********-1"></script>
    

    Should that be gtag.js instead of gtag/js?

  • Status changed to Needs work over 1 year ago
  • 🇺🇸United States jenlampton

    I think the latest patch is necessary, but changing status to needs work since the UA reporting is still problematic.

  • 🇺🇸United States japerry KVUO

    @jenlampton, you tried the patch in #125 -- and made sure the migration shim option was unchecked? If so, this would indicate an issue outside just the shim. It'd be nice if we didn't have to write two separate paths for UA vs GA4...

  • 🇺🇸United States srdtwc Skokie, IL

    Is there a plan for a new stable release for Drupal 7 with GA4 support, or is 2.6 considered the final release? I am wondering if it makes sense to wait for these issues to get ironed out before moving clients to GA4.

  • 🇵🇰Pakistan figover

    I have tested the patch 125 with dev version of Google Analytics module.
    It is working fine.
    I can see older analytics and G4 analytics both.
    I am sharing the screen shots of analytics from google analytics website
    Please check the screen shot of G4 analytics

  • 🇵🇰Pakistan figover

    I have tested this module on a website . Please check the website
    https://stockcafe.se
    You can see the page source account":["G-SVMTDMPWVQ","UA-166799864-1"]
    both accounts are working fine
    Please check the screen shot to see the UA analytics value
    Both are showing same record.
    We should launch this release with the patch 125 .

  • Status changed to Needs review over 1 year ago
  • 🇵🇰Pakistan figover

    Changing the status to needs review.

  • 🇺🇸United States mglaman WI, USA

    japerry is out on medical leave until the end of April. We didn't hear back from @jenlampton on #131 to verify that the patch works. But it seems like it is for others. Unfortunately, I cannot commit or make releases, and I do not believe japerry had access to add more folks.

    We'll need to reach out to the folks at Ixis with a handful of maintainers added.

  • 🇺🇸United States DamienMcKenna NH, USA

    @mglaman: Please let us know if you're contacting the Ixis folks or if someone else should, we don't want to spam them with twenty requests all at once. Thanks.

  • 🇺🇸United States mglaman WI, USA

    I'll reach out to them today

  • 🇺🇸United States mglaman WI, USA

    I contacted budda via Drupal.org contact form.

  • Status changed to Needs work over 1 year ago
  • 🇺🇸United States mglaman WI, USA

    Setting to Needs Work because this broke metrics and dimensions (already broken in D8 I believe)

        if (!empty($custom_map)) {
          // Add custom variables to tracker.
          foreach ($id_list as $id) {
            $custom_var .= 'gtag("config", ' . drupal_json_encode($id) . ', ' . drupal_json_encode($custom_map) . ');';
          }
          $custom_var .= 'gtag("event", "custom", ' . drupal_json_encode($custom_vars) . ');';
        };
    

    This should call set not config for custom dimensions to be set with all events

    Old code.

     $custom_var .= 'ga("set", ' . drupal_json_encode($googleanalytics_custom_type . $googleanalytics_custom_var['index']) . ', ' . drupal_json_encode($googleanalytics_custom_var['value']) . ');';
    

    Custom metrics probably don't do anything.

  • Assigned to mglaman
  • 🇺🇸United States mglaman WI, USA

    I was added as a maintainer. I'll work to get this fixed up today and tomorrow!

  • Issue was unassigned.
  • Status changed to Needs review over 1 year ago
  • 🇺🇸United States mglaman WI, USA

    Here is #125 rolled with fix for issue found in #140. I have emailed the Google Analytics team to get a confirmation on the implementation.

  • The last submitted patch, 142: interdiff-3174214-125-142.diff, failed testing. View results
    - codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

  • 🇺🇸United States mglaman WI, USA

    This should fix the test. I tried running tests locally but have other random failures.

  • The last submitted patch, 144: 3174214-144.patch, failed testing. View results
    - codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

  • 🇺🇸United States mglaman WI, USA

    Whoops! Local failures were due to $conf overrides polluting the test environment. This should fix the tests.

  • Status changed to Needs work over 1 year ago
  • 🇺🇸United States mglaman WI, USA

    I got an answer from Google Analytics and I was partially correct. The set command is for standard parameters. Custom dimensions and metrics must be passed into the config call.

    Example: of langcode dimension from [language:langcode] token.

    gtag('config', 'G-VX4WS8WWY9', {"langcode": "en"});
    
  • 🇺🇸United States mglaman WI, USA
    +++ b/googleanalytics.module
    @@ -258,28 +257,26 @@ function googleanalytics_page_alter(&$page) {
    -    $custom_var = '';
    -    if (!empty($custom_map)) {
    -      // Add custom variables to tracker.
    -      foreach ($id_list as $id) {
    -        $custom_var .= 'gtag("config", ' . drupal_json_encode($id) . ', ' . drupal_json_encode($custom_map) . ');';
    -      }
    

    This is correct, I was wrong. But the problem is that later on the following is re-configuring the measurement ID.

        foreach ($id_list as $id) {
          $script .= 'gtag("config", ' . drupal_json_encode($id) . ', ' . $arguments_json . ');';
        }
    
  • Status changed to Needs review over 1 year ago
  • 🇺🇸United States mglaman WI, USA

    Okay, this is a reroll against #125 with fixes for dimensions and metrics. Locally I'm having an error due to (float) 4.3 becoming 4.28888888889.

  • 🇺🇸United States mglaman WI, USA

    Sorry for more comment spam, identifying issue credits.

    • mglaman committed 8f4d7ac6 on 7.x-2.x
      Issue #3174214 by mglaman, MaxMendez, japerry, dsnopek, torgosPizza,...
  • Status changed to Fixed over 1 year ago
  • 🇺🇸United States mglaman WI, USA

    Thanks, everyone. I'm rolling a release. I'd like to tackle any follow ups in their own issue.

  • 🇺🇸United States DamienMcKenna NH, USA

    Thanks mglaman, and everyone else who contributed on this long-standing issue!

  • 🇺🇸United States philsward

    Any idea if the "cache local" problem was sorted out in the commit or does that still need a separate issue?

  • 🇺🇸United States mglaman WI, USA

    @philsward can you open a new issue? Because I think that feature needs to be removed if its causing issues.

  • 🇦🇺Australia standingtall

    After upgrading to new version, links tracked by gotwo module (below) stop tracking.

    Drupal.googleanalytics.isInternalSpecial = function (url) {
      var isInternalSpecial = new RegExp("(\/go\/.*)$", "i");
      return isInternalSpecial.test(url);
    };
    
  • 🇺🇸United States mglaman WI, USA

    @standingtall lease open an new issues with more details. What is gotwo? Did it break because our function changed? Thanks. We won't be reopening this issue.

  • 🇺🇸United States jenlampton

    Updating to 7.x-2.7 immediately made my site (a different site this time!) white screen. Nothing in the logs. Reverting to the previous version of the module brings the site back online. Once I find the cause I'll open a separate ticket.

  • 🇺🇸United States mglaman WI, USA

    @jenlampton I am super confused. Can you see if it's a PHP version issue? I don't know if we enforced PHP 7.4 somehow and maybe the server isn't PHP 7.4?

    Feel free to ping me on Drupal Slack to do throw around some debugging ideas. I would like to have higher confidence in this given the tight deadline for the EOL.

  • 🇺🇸United States jenlampton

    Yes, it was a PHP version issue, see https://www.drupal.org/project/google_analytics/issues/3349502 🐛 PHP short array syntax committed to 7.x-2.7 Fixed .

  • I updated to 2.7 yesterday and used the GA4 setup assistant on Google's site to create a new GA4 property, then put that new ID into the module settings separated from the old one by a comma. Since then, neither property has received any data. Is there a problem, or am I doing something wrong?

  • 🇺🇸United States dmundra Eugene, OR

    Hey @mglaman, check out this issue as well https://www.drupal.org/project/google_analytics/issues/3349458 🐛 drupalSettings is not defined error when using colorbox Fixed that I discovered in version 2.7.

  • Automatically closed - issue fixed for 2 weeks with no activity.

  • Status changed to Fixed about 1 year ago
  • 🇫🇮Finland sokru

    Sorry for being late for the party, but some of the patches in this issue have suggested accepting G- prefix for "Web Property ID", with the commit its not possible. Is that by purpose?
    There's a new variable googleanalytics_force_shim that has comment "Include the shim if configured to do so, when migrating from UA to GA4 tags.", meaning it should be enabled when using UA- or G- ID?

Production build 0.69.0 2024