How to make your WordPress theme compatible with Enhanced Ecommerce tracking
This page is written for theme developers. It lists what a WooCommerce theme has to keep in place so that the e-commerce tracking of Google Tag Manager for WordPress keeps working on it.
This page describes GTM4WP 2.1.0. Where an older version behaves differently, its behavior is marked with a bold GTM4WP 2.0.x: or GTM4WP 1.x: lead-in.
What is tracked
The plugin sends GA4 e-commerce events, which cover the whole funnel rather than the transaction alone. Among other things it reports which products were shown on which page and in which position, which of them were clicked, how often visitors used the cart button, and how that compares with the number of times the product was viewed.
The events are view_item, view_item_list, add_to_cart, remove_from_cart, view_cart, begin_checkout, add_shipping_info, add_payment_info and purchase.
GTM4WP 1.x: the plugin could also send the older Enhanced Ecommerce format of Universal Analytics, which is where the name of this page comes from. That format was removed in 2.0.0 along with Universal Analytics itself.
Rules of thumb
If you keep the hooks, HTML classes and IDs of the standard WooCommerce template files, your theme is already compatible. The plugin is tested against Storefront, which follows the WooCommerce conventions closely, so it is a good reference when you are unsure how a template is meant to look.
Where your theme departs from those conventions, work through the lists below and add back whatever you left out.
A div element around the whole page
Some themes wrap the entire page in a <div>. That is usually harmless, but it can interfere with the container code. The <noscript> part of the Google Tag Manager container belongs immediately after the opening <body> tag, and it must not end up inside a page wide wrapper element. Check the rendered page in the browser rather than the template file, because the wrapper is often added by a hook.
WooCommerce hooks the plugin relies on
The plugin attaches to the following WooCommerce and WordPress hooks. If your templates do not fire one of them, the matching part of the tracking stops working.
Product lists and product pages
woocommerce_after_shop_loop_item: fires in the product loop after each product has been printed. The plugin uses it to attach the data of each listed product.woocommerce_loop_add_to_cart_link: filters the add to cart link of a product in a list, so that a click on it can be reported with the right product.woocommerce_after_add_to_cart_button: fires on a product detail page after the cart button. The plugin uses it to track add to cart clicks.woocommerce_grouped_product_list_column_label: used to follow the rows of a grouped product.
GTM4WP 2.0.x: the plugin also used woocommerce_before_template_part and woocommerce_after_template_part as a pair to detect the classic Products widget, added a widget-product-item class to its items and reported the widget title from widget_title, followed by “(widget)”, as the list name. Tracking of that widget had not worked since 2017, so 2.1.0 removed this code. If a trigger in your container filters on an item_list_name that ends in “(widget)”, remove that condition.
List names
woocommerce_related_products_args,woocommerce_related_products_columns,woocommerce_upsells_columnsandwoocommerce_cross_sells_columns: these tell the plugin which of the secondary product lists on a product page is being rendered, so it can report a meaningful list name.woocommerce_shortcode_before_<shortcode>_loop, where the shortcode is one ofrecent_products,sale_products,best_selling_products,top_rated_products,featured_productsorrelated_products: the same idea for lists built with a shortcode.
GTM4WP 1.x: WooCommerce had no filter for the up-sell and cross-sell templates, so those lists could not be named. That gap is closed, and the two column filters above are now used.
Cart and checkout
woocommerce_cart_item_productandwoocommerce_cart_item_remove_link: used together to attach product data to the remove link of a cart row, which is what makes a completeremove_from_cartevent possible.woocommerce_cart_item_restored: fires when a visitor uses the Undo link after removing a product. The plugin reports an add to cart event for it.woocommerce_add_to_cart_fragments: used to keep the tracking data in step when WooCommerce refreshes the cart over AJAX.
Orders
woocommerce_thankyouandwoocommerce_is_order_received_page: used to report the purchase on the order received page.woocommerce_order_status_changedandwoocommerce_payment_complete: used by the fallback that reports a purchase when the visitor never reaches the order received page, for example because a payment gateway redirected them elsewhere.wp_footer: the plugin prints part of its tracking code here, so your theme has to call it.
Block based themes and the WooCommerce blocks
The plugin supports the WooCommerce block products grid through woocommerce_blocks_loaded and woocommerce_blocks_product_grid_item_html. On the frontend it looks for the standard block markup, so a grid item has to keep its wc-block-grid__product class inside a wc-block-grid wrapper.
If your theme rebuilds the product grid with its own markup instead of rendering the WooCommerce blocks, the plugin cannot recognise the items and the list tracking on those pages stops.
Class names and IDs used on the frontend
The frontend script finds the elements it has to watch through the class names and attributes that the standard WooCommerce templates produce. Keep the following in place.
product-quantityon the cart row, with the quantity<input>inside it carrying theqtyclass. Both are needed to report a changed quantity.update_cartas the name attribute of the Update cart button.quantityon the quantity wrapper of a product detail page, and aquantityname attribute on the input itself.variation_idas the name of the hidden input that carries the chosen variation on a variable product.payment_methodsas a class on the element that wraps the payment method list, with the radio inputs inside it. The inputs are also matched by a name starting withpayment_method.shipping_methodas the name of the shipping method radio inputs. Both are used to report the selected payment and shipping method.mini_cart_itemon each product of the mini cart widget.product-removeon the cell of a cart row that holds the remove link, andremoveon the remove link itself, in both the cart page and the mini cart widget.single_add_to_cart_buttonon the cart button of a product detail page.add_to_cart_buttonon the cart button of a product in a list.product-categoryon the links of product category items in a list, so that the plugin can tell them apart from product links.- Call the
post_class()WordPress function when you list products.
One more class appears in the page but comes from the plugin rather than from your theme, so you do not have to add it: gtm4wp_productdata, which carries the data of a tracked product.
GTM4WP 1.x: the cart page product list also had to sit in a table carrying both the shop_table and the cart classes, and the mini cart widget had to use a cart_list class. Neither is used any more, so a theme that dropped them is no longer affected.
Does the tracking need jQuery?
Almost never. The frontend script is written in plain JavaScript and finds its elements with querySelector, so a theme that does not load jQuery works for the most part.
There are two exceptions, both forced by WooCommerce itself. WooCommerce fires some of its own frontend events through jQuery, and the Quick View popup of some themes loads over jQuery AJAX calls that plain JavaScript cannot observe. The plugin listens to those through jQuery where it has no alternative. If your theme removes jQuery completely, the tracking that depends on those WooCommerce events stops while the rest keeps working.
GTM4WP 1.x: the whole frontend script was built on jQuery, so it needed jQuery on every page.
Related pages
See also the list of actions and filters the plugin provides, which includes the gtm4wp_eec_* filters you can use to change the e-commerce data itself.

