What GTM4WP 2.0 changes for WooCommerce stores
GTM4WP 2.0 changes seven things you should check in your plugin settings and Google Tag Manager container after upgrading a WooCommerce store.
GTM4WP 2.0 is a complete rewrite of this Google Tag Manager plugin, and the WooCommerce integration received the largest share of the work. Most stores can upgrade without editing anything in Google Tag Manager, but seven changes need your attention first.
- The
purchaseevent no longer fires for orders left in Pending payment. quantityis now always a number, so a trigger that compares it to text stops matching.- The hashed email address and phone number changed, so anything that stores them sees different values.
- Browser, operating system and device data arrives in its own
gtm4wp.deviceDataevent and needs a Custom Event trigger. - Customer data on the order received page is left out when WooCommerce would not show that order to the visitor.
- The dynamic remarketing product ID prefix is kept on variations.
- Weather data, geo data and scroll tracking are gone, so remove the variables and triggers that read them.
Your settings are migrated when 2.0 activates, and the data layer keys, template functions, filters and wp-config constants of 1.x are unchanged. Each one is explained below, followed by what the new version adds.
Where the WooCommerce settings live now
In 1.x every integration shared a single Integration tab. WooCommerce now has a settings section of its own, split into five option groups: General, Product data, Data layer content, Purchase tracking and Advanced. Your saved values are migrated, so there is nothing to enter again, but the option you are looking for may be one group away from where you remember it. The settings screen has a search field, and every option links to its own documentation page.
Track e-commerce, in the General group, is still the master switch. With it off the plugin writes no e-commerce data at all, the orderData and customer variables included.

What to check after the upgrade
The purchase event now follows the order status
1.x sent the purchase event for any order that reached the order received page unless that order had failed. 2.0 has a setting named Order statuses that trigger the purchase event, and it starts with Processing, On hold and Completed selected. The event now fires when the order is placed with one of those statuses, so a Cash on Delivery order (Processing) and a bank transfer order (On hold) are tracked at checkout even though the money arrives later.
Orders left in Pending payment are no longer tracked by default. If your reporting counted them, add that status to the setting. The list can also be changed in code with the new gtm4wp_purchase_trackable_statuses filter.
Quantity is always a number
add_to_cart and remove_from_cart now report quantity the same way everywhere. In 1.x the cart page could send it as text while the mini-cart sent a number for the same product, and a product form with no quantity field at all sent null with a value of 0. Check your container if a trigger or a variable compares quantity against a string.
The hashed email address and phone number changed
Google folds a tagged Gmail address such as [email protected] down to [email protected] before hashing it. 1.x removed only the dots, so every customer who used a tagged gmail.com or googlemail.com address produced a hash that could never match. Phone numbers had a similar problem: they were lowercased and stripped of spaces, but not converted to the international E.164 format that Google matches on, so a number typed the way it is dialed locally never matched either.
Both are fixed in 2.0, which means the values change in orderData.customer.billing.email_hash and phone_hash, in customerBillingEmailHash, and in the enhanced conversions user data. Nothing needs to be done in Google Tag Manager, but if you store or compare these hashes somewhere else, expect different values from now on. When a phone number cannot be converted with confidence, the field is sent empty instead of carrying a hash that would never match.
Device, browser and operating system data arrive in their own event
This one is not specific to WooCommerce, but it affects every site. Browser, operating system and device data is now collected in the browser with User-Agent Client Hints instead of being parsed from the user agent string on the server, and it arrives in a data layer event named gtm4wp.deviceData. Tags that read browserName, osName or deviceType need a Custom Event trigger on that event rather than a Page View trigger. deviceType now reports desktop or mobile only, because client hints do not identify a tablet reliably.
The product ID prefix is kept on variations
When a visitor selected a variation on a variable product page, 1.x swapped in the variation ID and dropped the Product ID prefix you configured for dynamic remarketing. The prefix is now applied to the variation as well, so the id field used for Google and Meta catalog matching is consistent across the site. The unprefixed item_id is unchanged. If you worked around this in your container or in your product feed, that workaround can go.
Customer data on the order received page
On the order received page, the customer details in orderData, the new customer signals and the enhanced conversions user data are now included only when WooCommerce itself would show that order to the visitor who is looking at the page. The purchase event, the order total and the items are unchanged and still fire exactly as before, so nothing is lost for a buyer who arrives from checkout. Someone who opens an old confirmation link without the matching session sees the purchase event without the personal data.
WooCommerce filters that relax its own checks are honored, so a store that has already customized this keeps the behavior it configured. Stores on WooCommerce releases older than 7.9, which have no such check at all, are not affected.
Three features were removed
Weather data, the ipstack-based geo data and scroll tracking are gone. The migration deletes their stored options, including any API key you had saved, so remove the variables and triggers in your container that read those values. Scroll depth is covered by the built-in Scroll Depth trigger in Google Tag Manager. The documentation pages of the weather and geo features name external replacements for the sites that need them.
Two settings that now do what they say
The Set maximum timeout for select_item event option, in the Advanced group, reached the page correctly in 1.x, but the tracker looked it up under the wrong name and never found it. Every product list click therefore waited the built-in 2000 ms for Google Tag Manager to call back. Setting it to 0 is the documented remedy when a consent tool blocks Google Tag Manager and the callback never arrives, and it takes effect now.
The tag restriction list, called blacklist and whitelist in 1.x, uses Google’s current wording: blocklist and allowlist. Blocklist mode used to restrict every tag, trigger and variable in the container instead of the ones you selected, which made the feature unusable. That is fixed, and the entity list was refreshed against Google’s documentation.
The checklist
| What changed | What to do |
|---|---|
The purchase event no longer fires for Pending payment orders | Add Pending payment to Order statuses that trigger the purchase event, if your reporting counted those orders. |
quantity is always a number | Check any trigger or variable that compares quantity to a string such as “1”. |
| The email and phone hashes changed | Nothing in Google Tag Manager. Update anything outside it that stores or compares those hashes. |
Device, browser and OS data moved to gtm4wp.deviceData | Move the tags that read browserName, osName or deviceType onto a Custom Event trigger for that event. |
| Customer data is withheld on some order received page views | Nothing for a buyer arriving from checkout. Expect empty customer fields when someone opens an old confirmation link. |
| The product ID prefix is kept on variations | Remove any workaround that re-added the prefix in your container or in your product feed. |
| Weather, geo and scroll tracking are gone | Delete the variables and triggers that read them, and use the built-in Scroll Depth trigger instead. |
What 2.0 adds for WooCommerce stores
The Cart and Checkout blocks are tracked
The React-based Cart and Checkout blocks are the WooCommerce default now, and they never fire the classic jQuery events, so add_to_cart, remove_from_cart, add_shipping_info and add_payment_info were simply lost on a block-based store. GTM4WP now publishes its GA4 item data on the WooCommerce Store API and reads the cart, checkout and payment data stores to send those events. view_cart, begin_checkout and purchase keep coming from the server. Because the item price arrives from the server as a real number, values stay correct in zero-decimal currencies such as the Japanese yen.
Block coverage goes further than the two big blocks. Removing an item or changing a quantity in the Mini-Cart drawer sends remove_from_cart. The Product Collection block, which fires none of the classic product-loop hooks, reports view_item_list and select_item with a readable list name for every collection WooCommerce ships, and the four collections that replace a legacy product grid block report the same list name that block did, so moving a page over does not start a new list in your reports. The cross-sell products in the Cart block report those two events as well.
There is nothing to configure. On a block Cart or Checkout page the block tracker loads and the classic tracker is skipped, so nothing is counted twice.
The purchase event reaches you more often
A missing purchase event is one of the most frequently reported problems with this plugin. Three settings work on it in 2.0, and two of them are new.
- The order status setting described above tracks orders that are paid later.
- A new Custom order received (thank-you) page option fires the
purchaseevent on a bespoke confirmation page, resolving the order from the buyer’s session. This is for themes and plugins that do not use the standard WooCommerce order received endpoint. - A new Reliable purchase tracking option covers the cases nothing else can reach. When the customer lands on a heavily customized thank-you page, or on the order-pay page instead of the order received page, the
purchaseevent is sent on the next page they open in the same browser session. The order is remembered on the server, and the event is de-duplicated against the order-tracked flag, the browser cookie and the order-age guard, so it cannot be counted twice. This one is experimental and off by default. It cannot recover an order where the buyer pays through an asynchronous gateway and never returns to the store, which still needs server-side tracking.
Better item data
Every tracked product list item now carries an item_list_id next to item_list_name. The id is a fixed identifier that does not change with the site language, so a multilingual store reports one list in GA4 instead of one list per translation. In the WooCommerce product grid blocks, every grid on a page is now a separate list instead of all of them sharing one name.
Items now carry a per-unit discount wherever it can be computed, on the cart, view_cart, begin_checkout and purchase events, calculated on the same tax basis as the item price. Lines that were not discounted carry no discount key. The view_item event sends an explicit quantity of 1, which the GA4 specification asks for.
There is also a new option named Persist product list attribution across the funnel, in the Product data group. When a visitor clicks a product in a list, the plugin remembers which list it was in a first-party cookie and carries that attribution onto the later view_item, add_to_cart, view_cart, begin_checkout, add_shipping_info, add_payment_info and purchase events, so GA4 can attribute a sale back to the list that started it. The product page keeps working behind a full-page cache, because the server still renders the view_item event and the browser fills in the list name. It is off by default on purpose: turn it on only if you are not already doing the same with custom JavaScript in your container, otherwise the attribution would be set twice.
Enhanced conversions and new customer reporting
The purchase event can now carry the user_data block that Google Ads and GA4 expect for enhanced conversions: SHA-256 hashed email address, phone number and name, plus the plain address components Google asks for. It is built from the order, so guest checkouts are covered too, and it is switched on with the existing Customer data in data layer option. In 1.x you had to assemble this yourself from the individual hash variables.
The purchase event also carries customer_type, the new or returning string documented in Google’s GA4 e-commerce reference. The new_customer boolean is unchanged and still sent, because Google Ads customer acquisition reads one and GA4 reads the other. Nothing needs to change in your container unless you want to use the new variable.
Transaction ID prefix
A new Transaction ID prefix option in the Purchase tracking group prepends your text to the transaction_id sent with the purchase event. It is useful for telling several stores apart in one GA4 property, or for matching the order id format of another system. It is empty by default, and only the purchase event is affected: the order number in orderData and the duplicate tracking guards keep using the raw WooCommerce order number.
CheckoutWC compatibility
CheckoutWC replaces the WooCommerce checkout with its own multi-step template, so the classic markers the tracker relies on are not reliably present and the add_shipping_info and add_payment_info events were missed. A new option in the Advanced group binds those steps to CheckoutWC’s own step change event, de-duplicated with the classic checkout. It is experimental and off by default, so turn it on only if your store runs CheckoutWC.
If you write code around the plugin
- A new
gtm4wp_eec_item_with_sourcefilter receives the raw source object an item was built from, the WooCommerce cart item or theWC_Order_Item, so you can copy custom line-item meta onto a GA4 item. The oldergtm4wp_eec_product_arrayfilter is deprecated in its favor, keeps working unchanged, and raises a notice only whenWP_DEBUGis on. - A new
gtm4wp_eec_item_affiliationfilter sets the item-levelaffiliationfield, which WooCommerce has no native value for and which stays out of the payload until you supply one. - The add to cart tracking logic is exposed as two functions,
gtm4wp_track_single_add_to_cartandgtm4wp_track_list_add_to_cart. A theme that handles Add to Cart with its own AJAX, and that callspreventDefault()on the button, can fire the event from its success handler in a couple of lines instead of copying the tracker. - The e-commerce tracker logs every event it sends to the browser console, unless you have turned off console messages in the plugin settings.
Problems you may have been living with
- Cart and checkout pages could exhaust the PHP memory limit on some WooCommerce versions, because a price display function was called once per cart item and again for each remove link. Item prices now come from the line totals WooCommerce has already calculated.
- With Exclude tax from revenue enabled, item prices on the
purchaseevent now exclude tax as well. Previously the transaction value was tax-exclusive while the item prices followed the shop display setting, so GA4 item revenue did not reconcile with the transaction total. - Variable subscription products keep their variant data. Variations were matched by an exact product type, and WooCommerce Subscriptions reports a different one, so
item_variant,item_group_idand the parent category and brand were dropped, most visibly on thepurchaseevent. - Adding a product to the cart no longer forces a full-page reload on stores using the blockified Add to Cart + Options block in WooCommerce 10.9 and newer. The plugin printed a hidden input into the add-to-cart form, and WooCommerce renders a classic full-page form whenever a plugin does that, so the interactive add to cart was disabled on every product page while GTM4WP was active. The product data now travels in a data attribute instead.
- The
[add_to_cart]shortcode button fires anadd_to_cartevent, and the product page no longer fires one when the browser blocks the submit because a required field is empty. - The WooCommerce Quick View popup pushes into the data layer variable you configured. It was the one place still using the default
dataLayername, so on a site that renamed the data layer the event was silently dropped. - The browser-side duplicate purchase guard works on stores whose order numbers are not the plain order id, such as sequential or prefixed order numbers. The cookie held the order number while the server compared it to the order id, so that leg of the de-duplication never matched.
- The trackers no longer fire their events twice when their script is loaded a second time on the same page, by an AJAX navigation or by a page builder that duplicates the script handle.
If your store runs a full-page cache
2.0 also brings the optional cache-safe data layer, which keeps visitor and customer values out of the cached HTML and lets the browser collect them instead. For a WooCommerce store the customer and cart blocks ride the cart fragments response that WooCommerce already refreshes on every cart change, so the plugin makes no extra request for them. It is off by default, and it needs Custom Event triggers in your container. The trigger setup page covers all four events.
Documentation
The four WooCommerce setup guides were rewritten for 2.0 and share one importable container template:
- Google Analytics 4 e-commerce tracking
- Google Ads conversion tracking
- Google Ads dynamic remarketing
- Enhanced Conversions for Google Ads
If something does not work on your store, or a data layer value is not what you expect, please open an issue so that it can be fixed: https://github.com/duracelltomi/gtm4wp/issues
