Question

I'm trying to add the new GA4 code to an existing site, and there seems to be some conflict / ordering issue between script_loader_tag filters and wp_add_inline_script. My functions.php looks thusly:

...
// Enqueue scripts
function cwr_enqueue_scripts() {
        wp_enqueue_script('cwr-google-analytics', get_stylesheet_directory_uri()
            . '/js/ga.js', array(), '1.0.3');
        wp_add_inline_script('cwr-google-analytics', "ga('create', "
            . "'" . CWR_GAUA_ID . "', 'auto');ga('send', 'pageview');");


        wp_enqueue_script('cwr-google-gtag', "https://www.googletagmanager.com"
            . "/gtag/js?id=" . CWR_GA4_ID, array(), null);
        wp_add_inline_script('cwr-google-gtag', "window.dataLayer = "
            . "window.dataLayer || [];"
            . "function gtag(){dataLayer.push(arguments);} gtag('js',"
            . "new Date()); gtag('config', '" . CWR_GA4_ID . "');");
}
add_action('wp_enqueue_scripts', 'cwr_enqueue_scripts');

add_filter('script_loader_tag', 'cwr_gtag_async', 10, 3); // problem causer
function cwr_gtag_async($tag, $handle, $src) {
        if ($handle === 'cwr-google-gtag') {
                $tag = '<script type="text/javascript" id="cwr-google-gtag-js"'
                    . 'src="' . esc_url($src) . '" async></script>';
        }

        return $tag;
}
?>

As is, it outputs three lines, and never adds the "after" script to cwr-google-gtag:

...
<script type="text/javascript" id="cwr-google-gtag-js" src="https://www.googletagmanager.com/gtag/js?id=<my_id>" async></script>
<script type="text/javascript" src="https://www.example.com/wp-content/themes/vantage-child/js/ga.js?ver=1.0.3" id="cwr-google-analytics-js"></script>
<script type="text/javascript" id="cwr-google-analytics-js-after">
ga('create', '<my_id>', 'auto');ga('send', 'pageview');
</script>
...

If I comment out the script_loader_tag filter, then I get my extra code added, but obviously lose the async attribute:

...
<script type="text/javascript" src="https://www.example.com/wp-content/themes/vantage-child/js/ga.js?ver=1.0.3" id="cwr-google-analytics-js"></script>
<script type="text/javascript" id="cwr-google-analytics-js-after">
ga('create', '<my_id>', 'auto');ga('send', 'pageview');
</script>
<script type="text/javascript" src="https://www.googletagmanager.com/gtag/js?id=G-GCBJ73QR90" id="cwr-google-gtag-js"></script>
<script type="text/javascript" id="cwr-google-gtag-js-after">
window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);} gtag('js',new Date()); gtag('config', '<my_id>');
</script>
...

Any ideas what's wrong here? Or, is there a better canonical way to get this to work?

Was it helpful?

Solution

The reason this is happening is because the markup that is filtered by script_loader_tag includes the inline scripts. So when you filter it and replace all the HTML tag for a particular script, your filter is removing those inline script tags. If you print out the original value of $tag from within your filter you will see this.

You can look at the source of WP_Scripts::do_item() to see how the inline scripts are prepended and appended. With your approach you would need to replicate all that logic. The better approach is to just use str_replace to replace part of the script tag with an amended part, like this:

if ( $handle === 'cwr-google-gtag' ) {
    $tag = str_replace(
        'id="cwr-google-gtag-js"',
        'id="cwr-google-gtag-js" async',
        $tag
    );

    return $tag;
}

That will add async after the correct script tag's ID attribute.

All that being said, your approach is not going to work. If ga.js is loaded asynchronously, then the subsequent inline script tag is going to run before ga.js has loaded, which will cause an error because ga() has not been defined by the time this runs:

ga('create', '<my_id>', 'auto');ga('send', 'pageview');

See this example from Google's documentation for loading Google Analytics asynchronously:

<!-- Google Analytics -->
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<!-- End Google Analytics -->

For this to work you need an inline script before the analytics script is loaded, and that script needs to include a line that defines ga() if it has not been defined yet.

Lastly, you should not be hosting Google Analytics in your theme. As advised by Google:

While it's possible to download the JavaScript file (gtag.js) to examine it, storing or serving the JavaScript file locally is not recommended.

Referencing the JavaScript file from Google's servers (i.e., https://www.googletagmanager.com/gtag/js) ensures that you get access to new features and product updates as they become available, giving you the most accurate data in your reports.

You should consult Google's documentation to make sure Google Tag Manager doesn't also need to be loaded a particular way, when adding it asynchronously.

Licensed under: CC-BY-SA with attribution
Not affiliated with wordpress.stackexchange
scroll top