=== GTM4WP - A Google Tag Manager (GTM) plugin for WordPress === Contributors: duracelltomi Donate link: https://gtm4wp.com/ Tags: google tag manager, tag manager, gtm, google ads, google analytics Requires at least: 6.3 Requires PHP: 8.0 Tested up to: 7.1 Stable tag: 2.0.1 License: GPLv3 License URI: http://www.gnu.org/licenses/gpl.html Advanced tag management for WordPress with Google Tag Manager == Description == Google Tag Manager (GTM) is Google's free tool for everyone to manage and deploy analytics and marketing tags as well as other code snippets using an intuitive web UI. To learn more about this tool, visit the [official website](https://marketingplatform.google.com/about/tag-manager/). This plugin places the GTM container code snippets onto your WordPress website so that you do not need to add them manually. Multiple containers are also supported, and each container can have its own environment parameters, custom domain and custom path. The plugin complements your GTM setup by pushing page meta data and user information into the so-called data layer. Google's official help pages include [more details about the data layer](https://developers.google.com/tag-platform/tag-manager/datalayer#datalayer). **PHP 8.0 and WordPress 6.3 are required to use this plugin.** = GTM container code placement = The original GTM container code is divided into two parts: The first part is a javascript code snippet that is added to the `
` section of every page of the website. This part is critical to enable all features of GTM, and this plugin helps to place this part correctly on your site. The second part is an iframe snippet that acts as a failsafe/fallback should users' JavaScript be disabled. Google recommends – for best performance – to place this code snippet directly after the opening `` tag on each page. Albeit not ideal, it will work when placed lower in the code. This plugin provides a code placement option for the second code snippet. If your WordPress theme is compatible with the additions of WordPress 5.2, then this plugin will place this second snippet in the right place. Users of the Genesis theme, GeneratePress theme, Elementor, Oxygen Builder and Beaver Builder Theme will also have it placed correctly. To utilize this, set the compatibility mode in the plugin options to off. All other users can place this second code snippet using custom PHP code (the "Manually coded" option), or select the so-called "Footer" option to add the code lower on the page (this is not the recommended way, but it will work). = Basic data included = * post/page titles * post/page dates * post/page category slugs * post/page tag slugs * post/page author ID and name * post/page ID * post types * post format * post count on the current page + in the current category/tag/taxonomy * custom terms associated with any post type * logged in status * logged in user role * logged in user ID (to track cross device behavior in Google Analytics) * logged in user email address (both unhashed and SHA256 hashed values to be used with tracking) * logged in user creation date * site search data * site name and id (for WordPress multisite instances) * IP address of the visitor (please obtain the visitor's explicit consent before using this) * post author data from PublishPress Authors, including co-authors and guest authors = Content & engagement data = Optional page variables that help with behavior tracking and Google Analytics 4 content grouping: * content word count and estimated reading time * last modified date and content age in days * comment count and comment status * page template, featured image presence, page hierarchy and sticky flag * primary category, detected from Yoast SEO or Rank Math * page language, detected from WPML or Polylang = Browser / OS / Device data = * browser data (name, version, engine) * OS data (name, version) * device data (type, manufacturer, model) Data is collected in the browser using User-Agent Client Hints and pushed as a gtm4wp.deviceData event. Note that Safari and Firefox expose less detail than Chromium based browsers. = Media player events = Track users' interactions with any embedded media: * YouTube * Vimeo * Soundcloud * HTML5 audio and video * Dailymotion * Mixcloud * Cloudflare Stream * Wistia * JW Player * VideoPress * Spotify * Twitch You can choose to fire data layer events when the media player loads, when the media is played, when it is paused or stopped, and optionally when the user reaches 10, 20, 30, ..., 90, 100% of the media duration. Each event also populates Google Tag Manager's built-in Video variables (Video Status, Video URL, Video Title, Video Provider, Video Duration, Video Current Time, Video Percent, Video Visible). Tracking is supported for embedded media using the built-in oEmbed feature of WordPress, as well as most other media plugins and copy/pasted embed codes. Players inserted into the page after it has loaded (for example in a popup, a lightbox or via AJAX) can also be tracked by turning on the optional "Track dynamically inserted players" setting. = Tag restrictions: allowlist & blocklist Tag Manager tags, triggers and variables = To increase website security, you have the option to allowlist or blocklist tags, triggers and variables. You can prevent specific tags from firing, or prevent the use of certain variable types, regardless of your GTM setup. If the Google account associated with your GTM account is compromised, an attacker could easily execute malware on your website without accessing its code on your hosting server. By blocklisting custom HTML tags, custom JavaScript variables and sandboxed scripts (custom tag and variable templates), you can secure the Tag Manager container. = Integration = Google Tag Manager for WordPress integrates with several popular plugins. More integrations to come! * Contact Form 7: fire an event when a form is submitted with any result (mail sent, mail failed, spam detected, invalid input, submission aborted or terms not accepted). Optionally push the Google Analytics 4 recommended form events (form_start, form_submit, generate_lead) as well * WooCommerce: * Implementation of [GA4 E-commerce](https://developers.google.com/tag-manager/ecommerce-ga4) * Support for the Cart, Checkout, Mini-Cart, Product Collection and cross-sell blocks, not only the classic shortcode based pages * Enhanced Conversions user data on the purchase event for Google Ads * Compatibility with High Performance Order Storage (HPOS) * Does not support promotions, since WooCommerce does not have such a feature (yet) * Does not support refunds * CheckoutWC: optional support for its multi step checkout template * PublishPress Authors: co-author and guest author data in the page variables * AMP: load your AMP container on the AMP version of your pages * Google Consent Mode v2: fire the "default" command with specific consent flags to integrate with non-certified Consent Management Platforms (CMPs) and plugins * Cookiebot: use automatic cookie blocking mode if needed * Axeptio: load the Axeptio SDK and push every consent change into the data layer * CookieYes: push a data layer event whenever the visitor's consent changes = Server side containers = If you are using a [server side container](https://developers.google.com/tag-manager/serverside/send-data#update_the_gtmjs_source_domain), you can enter your custom domain name and custom path to load gtm.js from there. Both can be set per container, so you can mix server side and standard containers on the same site. = Cache-safe data layer = (experimental, off by default) On sites using a full page cache (LiteSpeed, WP Rocket, Varnish, Cloudflare APO), the HTML generated for one visitor is served to everyone else as well. Any visitor specific value written into the data layer would therefore leak to other visitors - the classic example being a page cached while an editor was logged in, then served to anonymous visitors with that editor's email address and role still in the data layer. When this option is enabled, no visitor or session data is written into cacheable HTML at all. Those values are delivered in the browser instead, under the same data layer variable names, so your existing Google Tag Manager setup keeps working. = Export & import your settings = You can export all plugin settings into a JSON file and import them on another site, which makes it easy to roll out the same configuration across several websites. Imported files are treated as untrusted and every value is validated before it is stored. = Exclude specific user roles from being tracked = You can set which user roles need to be excluded from tracking when a user with that role visits the frontend. This will completely disable the container code for that user. = Staging and development sites = The container can be limited to production environments only, so a cloned or staging copy of your site does not send data into your production Google Tag Manager container. This relies on the WP_ENVIRONMENT_TYPE setting of WordPress. = For developers = Version 2.0 is a complete object oriented rewrite. Every feature is a module, and third party plugins can register their own modules through the gtm4wp_register_modules action. All public template functions, filter and action names, wp-config constants and the option storage key of the 1.x versions are unchanged, so existing integrations keep working. == Installation == 1. Upload `duracelltomi-google-tag-manager-for-wordpress` to the `/wp-content/plugins/` directory 1. Activate the plugin through the 'Plugins' menu in WordPress 1. Go to Settings / Google Tag Manager and enter your Google Tag Manager container ID and set additional options == Frequently Asked Questions == = How can I ... = Tutorials for various Google Tag Manager settings and implementation are available on the plugin's website: https://gtm4wp.com/setup-gtm4wp-features = PayPal / 3rd party payment gateway transactions in WooCommerce are not being tracked in Google Analytics = PayPal and some other 3rd party payment gateways do not redirect users back to your website after a successful transaction by default. They offer a route back for your customer, but it can happen that users close the browser before arriving at your thank you page (also known as the order received page). This means that neither Google Analytics tags nor any other tags have a chance to fire. Enable auto-return in your payment gateway settings. This will instruct the gateway to show a quick info page after payment and redirect the user back to your site. This will improve the accuracy and frequency of tracked transactions. = Purchase event is not tracked with WooCommerce = This can happen if you are using a 3rd party plugin that alters the default order received page in a way that does not use WooCommerce's integration hooks. Either stop using that plugin, or ask its authors to mimic the behavior of the default order received page more closely by supporting the woocommerce is_order_received_page function and the woocommerce_thankyou action. Since version 2.0 the plugin also offers two settings that work around this without changing the 3rd party plugin: "Custom order received (thank-you) page" fires the purchase event on a bespoke confirmation page, and "Reliable purchase tracking" emits a missed purchase event on the next page the customer views in the same browser session. Both are de-duplicated, so an order is never counted twice. = Why isn't there an option to blocklist tag/variable classes = Although Google recommends blocklisting tags and variables using classes, people struggle to know which tags and variables get affected. Therefore I opted for individual tags and variables rather than classes on the tag restriction tabs. Regarding variables: ensure they are not part of any critical tags, as blocklisting such variables will render those tags useless. = How can I track scroll events in Google Tag Manager? = Google Tag Manager supports basic scroll depth tracking based on percentage or pixels natively. Add a Scroll Depth trigger in your container and use it to fire your Google Analytics 4 and/or Google Ads remarketing/conversion tags. (The plugin's own scroll tracking feature was removed in 2.0 in favor of this built-in GTM functionality.) = I use a page cache. Can visitor data leak between visitors? = Yes, this is a real risk with any full page cache, and it is not specific to this plugin: the HTML generated for one visitor is stored and served to everyone else. If visitor specific values (email address, user role, IP address) are written into the data layer, the cached copy will contain the values of whoever happened to trigger the caching. Turn on the "Cache-safe data layer" option to avoid this. With it enabled, no visitor or session data is written into cacheable HTML; those values are delivered in the browser instead, under the same data layer variable names. = Why does the plugin load several separate JavaScript files instead of one? = Each tracking feature (WooCommerce, each media player, Contact Form 7, device data, etc.) is its own small JavaScript file, and the plugin loads only the files a page actually needs - the YouTube tracker, for instance, loads only on pages that embed a YouTube video, and with the "defer" strategy so it never blocks rendering. Those files are already minified by the plugin's build. Merging several files into one is intentionally left to a caching / performance plugin (WP Rocket, Autoptimize, LiteSpeed Cache, etc.), which can combine scripts across your whole site and in a way that suits your hosting and HTTP setup. On modern HTTP/2 hosting, many small conditionally-loaded files usually perform as well as one combined file. (1.x combined its own scripts; 2.0 delegates this.) == Screenshots == 1. Admin panel 2. Basic settings 3. Events 4. Integration panel 5. Advanced settings == Changelog == = 2.0.1 = * Fixed: on a block-based store, opening the Cart page pushed add_shipping_info and add_payment_info into the data layer with no interaction, and both events then fired again on the Checkout page. The block tracker told the two pages apart by the presence of the WooCommerce payment data store, which WooCommerce registers on the Cart page as well; the Cart and Checkout pages now each receive their own context and the checkout-step events fire only on the Checkout page. * Fixed: a blank settings screen no longer stays silent about why it is blank. Some ad and privacy blocker filter lists block everything under the plugin folder, including the file that builds the settings screen in the browser, which left the settings page empty with no explanation. A static notice now appears after a few seconds whenever the settings app could not start, explaining the most likely cause and the workaround (pause the blocker for the admin area of the site, or add an exception for it). = 2.0.0 = Major rewrite of the plugin. Please read the announcement post on gtm4wp.com before upgrading! **Architecture & requirements** * Changed: complete object-oriented rewrite. Every feature is now a module that third-party plugins can extend through the `gtm4wp_register_modules` action. All public template functions (`gtm4wp_the_gtm_tag()` etc.), filter/action names, wp-config constants and the `gtm4wp-options` storage key are unchanged, so existing integrations keep working. * Changed: minimum requirements raised to PHP 8.0 and WordPress 6.3. Supported up to WordPress 7.1. * Updated: frontend scripts now load with the `defer` strategy where possible. * Changed: the main plugin file now refuses to run when it is requested directly instead of loaded by WordPress, matching every other PHP file in the plugin. * Deprecated: the `$gtp4wp_plugin_url`, `$gtp4wp_plugin_basename` and `$gtp4wp_script_path` global variables (note the `gtp4wp_` spelling, a typo inherited from 1.x). Nothing inside the plugin reads them; they are set only for third-party code written against 1.x. They still work in this version but will be removed in GTM4WP 2.1. Use `plugin_dir_url( GTM4WP_PLUGIN_FILE )`, `plugin_basename( GTM4WP_PLUGIN_FILE )` and `plugin_dir_url( GTM4WP_PLUGIN_FILE ) . 'build/'` instead, all of which work on admin and frontend requests alike. Note that `$gtp4wp_script_path` already resolves to `build/` rather than the 1.x `dist/js/`, so any code building a script URL from it needs updating in any case. Unlike a deprecated function, a global variable cannot raise a deprecation notice, so this entry is the only warning there is. **Settings screen** * Added: modern React-based settings screen with left pane navigation, tabbed option groups, option search and inline per-field validation. The screen is usable on a phone: the title and the "Export settings" / "Import settings" / "Save changes" buttons stack instead of running off the edge of the screen; the list of settings sections becomes a search field and a dropdown pinned to the top of the screen, so switching section no longer means scrolling past a full screen of section names to reach the settings; a row of option group tabs that is wider than the screen scrolls sideways with each tab on one line, instead of being squeezed into columns of single words with the last tabs unreachable; and the container table turns into one card per container with every column labelled above its own field, instead of six columns crammed into the width of a phone. The selected option group tab is also kept in view now on any screen size, so opening a bookmark or an admin notice link no longer leaves the tab strip pointing at the wrong tab. * Added: admin notices now link straight to the option they are about. Clicking through opens the module, switches to the tab holding that setting and highlights it, instead of dropping you on the settings screen to work out for yourself that, say, the trusted proxy addresses live under Page variables -> Visitor data. * Added: the settings screen keeps its address bar up to date, so any module and tab can be bookmarked or sent to a colleague and reopens exactly where you left it. The back button still simply leaves the settings screen; it does not turn into a tab stepper. * Added: **a help link on every option**. Each settings section now carries a documentation link in its header, and every one of the plugin's 110 options has a "?" icon next to it that opens the part of gtm4wp.com describing that particular setting - what it changes in the data layer, what it is set to before you touch it, and when it is worth changing. Previously the documentation existed but nothing on the settings screen pointed at it, so finding the page for, say, "Only track orders younger than" meant searching the site for it. The links open in a new tab and never change a setting when clicked. Three sections that had no documentation page at all - the Google Tag Manager container settings, AMP and tag restrictions - have one now. Plugins that add their own modules can point their options at their own documentation, either by giving a full URL in their settings schema or through the new `gtm4wp_admin_doc_url` filter. * Added: **export & import of the plugin settings** (Google Tag Manager settings screen, next to "Save changes"). "Export settings" downloads all of your GTM4WP options as a JSON file; "Import settings" reads such a file back on another site (or the same one). The import is treated as untrusted: every value in the file is run back through the exact same per-field sanitizers as a normal save before anything is stored - unknown keys are dropped, every value is normalized to the field's expected type, oversized files are refused and the file is only ever parsed with `json_decode()` (never `unserialize`/`eval`), so a hand-edited or malicious file cannot inject unsafe data. Both endpoints require the settings capability and a nonce. The value set is schema driven, so every current and future option is covered automatically. **Google Tag Manager container & tag restrictions** * Added: every Google Tag Manager container ID now has its own environment parameters (`gtm_auth`/`gtm_preview`), custom domain and custom path, managed in a data table (new `gtm-containers` option). Existing settings are migrated automatically; the flat 1.x option keys are kept in sync for third-party code and downgrades. * Changed: with environment parameters configured, all containers are loaded now (1.x only loaded the first container in that case). Only the hard-coded wp-config environment constants still limit output to the first container. * Added: per-container "Omit container ID" option in the container table. When a custom path is set (server side GTM), turning it on drops the container ID from the loader URL (`gtm.js?'+dl` instead of `gtm.js?id='+i+dl`) for setups where the container is selected by its path. * Added: a kill switch to stop the Google Tag Manager container from loading on a cloned or staging copy of a site without deactivating the plugin. A new **"Only output the container on production environments"** option (Google Tag Manager container → Advanced) emits the container only when WordPress reports the environment type as "production" (set `WP_ENVIRONMENT_TYPE` on your non-production copies). Because `WP_ENVIRONMENT_TYPE` lives in `wp-config.php` or the server config and is invisible from the admin, the option's description reports the environment type WordPress actually returns on *this* site and whether the container would therefore be loaded or suppressed - including the common trap that an unset `WP_ENVIRONMENT_TYPE` falls back to "production", so the option silently keeps loading the container. For host-based control from an mu-plugin or `wp-config.php`, the new `gtm4wp_output_container` filter (default `true`) can veto the container from PHP. In both cases the data layer stays active and only the container `