Actions and filters in GTM4WP for developers
As a developer you can change the behavior of GTM4WP in many ways. This page lists every WordPress action and filter the plugin provides. If you are new to WordPress hooks, read the official documentation first.
This page describes GTM4WP 2.1.0. Hooks added by 2.0.0 or 2.1.0 are marked New in 2.0.0 or New in 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. Hooks that 2.0.0 or 2.1.0 removed are listed at the bottom.
The hook names below are the literal strings you pass to add_filter() and add_action(). Most of them also have a PHP constant defined in the plugin, for example GTM4WP_WPFILTER_COMPILE_DATALAYER. The string values are part of the public API and do not change between versions, so either form works.
Container code and data layer output
gtm4wp_compile_datalayer filter
GTM4WP uses this filter to build the data layer in the <head> section, before the main container code.
Hook into it to add your own data layer variables on page load. Return an associative array, which may contain nested arrays. GTM4WP encodes it for the script context itself, so pass raw values and do not escape or encode them yourself.
function my_filter( array $dataLayer ): array {
$dataLayer["myCustomVar"] = 'Hello World!';
return $dataLayer;
}
add_filter( 'gtm4wp_compile_datalayer', 'my_filter' );
When to register your data layer callback
This is the most common reason a correct looking callback never adds anything to the data layer. The filter is applied while WordPress builds the <head> section, from a callback on wp_head at priority 10. Your add_filter() call has to have run before that moment. If it runs later the data layer has already been compiled and written into the page, so your function is never called at all.
The deadline moves earlier when the Load GTM container as early as possible option is enabled, because the container code then runs on wp_head at priority 2 instead of 10. Code that registers itself late in the <head> can therefore work on one site and fail on another that has this option turned on.
These are good places to register the filter:
- The
functions.phpfile of your theme or child theme, at the top level of the file rather than inside another callback. - A small plugin or must use plugin of your own, again at the top level of the file.
- A callback on an action that runs earlier in the request, for example
init,wp,template_redirectorwp_enqueue_scripts. All of them finish beforewp_headstarts.
These are too late and will not work:
wp_footer,get_footer, or anything else that runs while the body of the page is being rendered.- The render callback of a shortcode or a block, and
the_contentfilters. Post content is rendered after the<head>has been built. - Another
wp_headcallback registered at priority 10 or later. If you have to register from insidewp_head, use priority 1, and remember that the early loading option above moves the deadline to priority 2.
One more detail if you work in a must use plugin. GTM4WP defines its GTM4WP_WPFILTER_* constants when the main plugin file loads, and must use plugins load before regular plugins, so the constant does not exist yet at that point. Use the literal string 'gtm4wp_compile_datalayer' there, which always works, or register your filter from a callback on plugins_loaded or later.
The same timing applies to the other hooks in this section, because they all run in the same pass over the <head>.
gtm4wp_output_after_datalayer action
Fires after the first data layer push and before the container starts to load. Use it to echo your own HTML or script block between the two. It receives no arguments and returns nothing, so echo your output directly, including the surrounding <script> tag.
function my_action(): void {
echo '<script>/* runs before the container loads */</script>';
}
add_action( 'gtm4wp_output_after_datalayer', 'my_action' );
Note that this is an action and not a filter. Earlier versions of this page called it gtm4wp_after_datalayer and described it as a filter that returns a string, which was wrong in every plugin version.
gtm4wp_after_container_code action
Fires after the main container code has been written into the page. Use it to push additional data layer events that have to arrive once the container is loading. It receives no arguments.
gtm4wp_output_container filter
New in 2.0.0. Decides whether the container code is written into the current page, both the <head> loader and the <noscript> iframe. It defaults to true.
Return false to suppress the container while the data layer stays in place. This is the supported way to stop a staging or cloned copy of a site from sending hits to your production container without deactivating the plugin. Because a must use plugin loads everywhere, it is also the way to skip the container on selected sites of a multisite network.
add_filter( 'gtm4wp_output_container', function( bool $output ): bool {
return 'production' === wp_get_environment_type();
} );
gtm4wp_header_top_inline_js filter
New in 2.0.0. Appends JavaScript to the inline block that sets up the data layer, before the container loads. It receives an empty string and the name of the data layer variable, and returns the JavaScript to add, without a <script> tag. GTM4WP uses it for its consent tool integrations.
The block is filtered with wp_kses(), so your JavaScript must not contain the characters < or > or a literal &, or the whole block stops working.
add_filter( 'gtm4wp_header_top_inline_js', function( string $inline_js, string $datalayer_name ): string {
return $inline_js . 'window.myFlag = true;';
}, 10, 2 );
gtm4wp_add_global_vars_array filter
GTM4WP declares a few global JavaScript variables that carry plugin settings to its own frontend scripts. They are written into a <script> block placed as high in the <head> section as possible. Use this filter to add your own entries.
The filter takes and returns an array of values keyed by variable name. GTM4WP encodes the array for the script context, so pass raw values rather than a piece of JavaScript source.
function my_filter( array $global_vars ): array {
$global_vars['my_global_var'] = false;
return $global_vars;
}
add_filter( 'gtm4wp_add_global_vars_array', 'my_filter' );
GTM4WP 1.x: the hook was called gtm4wp_add_global_vars and took a string of JavaScript source that you appended to. Rewrite such a callback for the array form, because the old name is no longer applied.
gtm4wp_get_csp_nonce filter
Returns the nonce value that GTM4WP adds to the script tags it writes, for sites that run a Content Security Policy. It defaults to an empty string, which adds no nonce attribute.
add_filter( 'gtm4wp_get_csp_nonce', function( string $nonce ): string {
return my_csp_nonce_value();
} );
gtm4wp_visitor_scoped_fields filter
New in 2.0.0. Used by the cache-safe data layer to collect the visitor specific fields that must stay out of the cached page HTML. Callbacks receive an array and append VisitorField objects, each carrying the delivery tier that decides whether the value is computed in the browser or fetched from the first party endpoint.
This hook belongs to a feature that is still marked experimental, so treat its shape as subject to change.
Page and content data
gtm4wp_page_language filter
New in 2.0.0. Filters the language code written into pageLanguage. GTM4WP detects it from WPML or Polylang and falls back to the site locale. Use this filter when your site runs on another multilingual plugin.
Receives one string, the detected language code, and returns the code to use.
gtm4wp_master_language_post_id and gtm4wp_master_language_term_id filters
New in 2.1.0. Used by the four default-language options: Output values in the default language for the page variables, and Report products in the default language, Report downloads in the default language and Report the form name in the default language for WooCommerce, Easy Digital Downloads and Contact Form 7. GTM4WP finds the post or term in the default language of the site through WPML or Polylang. Use these filters to support another multilingual plugin.
Without WPML or Polylang, the settings screen shows these four options as disabled. A callback on either filter enables them. The settings screen checks for the callback itself, so register it where it also runs in wp-admin, for example at the top level of a plugin, and not from a hook that only fires on the frontend.
Both receive the resolved ID (the original ID when nothing was found), the original ID in the current language, and the post type or the taxonomy. Return the ID to read the default-language values from.
function my_filter( int $resolved_id, int $id, string $type ): int {
return my_find_original_post( $id ) ?: $resolved_id;
}
add_filter( 'gtm4wp_master_language_post_id', 'my_filter', 10, 3 );
gtm4wp_reading_time_wpm filter
New in 2.0.0. Sets the reading speed used to work out pageReadingTime. Receives one int, the default of 200 words per minute, and returns the rate to use.
gtm4wp_primary_category_term_id filter
New in 2.0.0. Chooses the term reported in pagePrimaryCategory and pagePrimaryCategoryName. GTM4WP reads the primary category from Yoast SEO or Rank Math and falls back to the first category of the post. Use this filter for another SEO plugin or a custom taxonomy.
function my_filter( int $primary_category_id, int $post_id ): int {
// 0 means nothing was detected
return $primary_category_id;
}
add_filter( 'gtm4wp_primary_category_term_id', 'my_filter', 10, 2 );
gtm4wp_post_meta_in_datalayer filter
Decides whether one custom field reaches the data layer under pagePostTerms.meta. It runs once per meta key and defaults to true. Return false to leave a key out.
This matters because the custom fields option publishes every meta key that does not start with an underscore into the public page, so it is the supported way to keep internal fields private.
function my_filter( bool $include, string $post_meta_key ): bool {
return 'my_internal_note' !== $post_meta_key;
}
add_filter( 'gtm4wp_post_meta_in_datalayer', 'my_filter', 10, 2 );
gtm4wp_page_post_authors and gtm4wp_page_post_author_ids filters
New in 2.0.0. Filter the author names and author IDs of a post that has several authors through PublishPress Authors. The first receives an array of display names, the second an array of IDs, and both also receive the PublishPress author objects the values were built from. Guest authors have no WordPress user account and are reported with a negative ID.
Consent mode
gtm4wp_overwrite_consent_mode_flag filter
Overwrites the stored default value of a single consent mode flag. The callback receives the stored value and the name of the flag, so you can vary a default by audience or by request.
function my_filter( $flag_value, string $flag ) {
if ( 'ad_storage' === $flag ) {
return 'granted';
}
return $flag_value;
}
add_filter( 'gtm4wp_overwrite_consent_mode_flag', 'my_filter', 10, 2 );
Return true or the string 'granted' to grant the signal, false or the string 'denied' to deny it.
GTM4WP 2.0.x: the return value is only checked for being true or false, so the string 'denied' grants the signal. Return false to deny it.
gtm4wp_consent_mode_default_enabled filter
New in 2.0.0. Decides whether GTM4WP writes its own Google consent mode default command into the page. Return false when your consent tool sends the default command itself, so the page does not get two of them.
gtm4wp_axeptio_consent_mode_default filter
New in 2.0.0. Filters the Google Consent Mode v2 default state that the Axeptio integration hands to the Axeptio SDK. The plugin denies every signal by default. Use this filter to grant signals for audiences outside the GDPR area.
WooCommerce e-commerce data
Several of these filters receive $attributes_used_for, which names the e-commerce context the item is being built for. Its possible values are purchase, cart, checkout, productdetail, readdedtocart, addtocartsingle, widgetproduct, productlist and groupedproductlist.
gtm4wp_eec_item_with_source filter
New in 2.0.0. The successor of gtm4wp_eec_product_array and the filter to use for new code. It behaves the same way but receives a third argument: the raw source object the item was built from. That is the WooCommerce cart item array on the cart and checkout paths, or the WC_Order_Item on the purchase path. It is null where there is no per line source, such as a product detail page or a product list.
The point of the third argument is that custom cart and order item meta never lives on the product or variation object, so it could not be reached from the older filter. The source object is never merged into the item array, which lets you attach only the fields you need instead of enlarging every event.
function my_item_filter( array $item, string $attributes_used_for, $source_item ): array {
if ( is_array( $source_item ) && isset( $source_item['my_custom_meta'] ) ) {
$item['my_field'] = $source_item['my_custom_meta'];
}
return $item;
}
add_filter( 'gtm4wp_eec_item_with_source', 'my_item_filter', 10, 3 );
gtm4wp_eec_product_array filter
Deprecated. Still applied, so existing code keeps working, but use gtm4wp_eec_item_with_source in anything new.
Applied after GTM4WP has prepared a product array. Use it to add your own fields or to change existing ones. The filter runs once per product, so on a cart holding three products it runs three times.
function my_product_filter( array $eec_product, string $attributes_used_for ): array {
if ( $attributes_used_for === "productdetail" ) {
$eec_product["name"] .= " - Order today!";
}
return $eec_product;
}
add_filter( 'gtm4wp_eec_product_array', 'my_product_filter', 10, 2 );
gtm4wp_eec_cart_item filter
Decides whether a cart item is reported to Google Tag Manager. WooCommerce has its own filter for products that count as hidden, and this one hides a product from GTM reporting only. It defaults to true.
function my_cart_item_filter( bool $item_included, array $cart_item ): bool {
return false; // false hides the product from GTM reporting in the cart
}
add_filter( 'gtm4wp_eec_cart_item', 'my_cart_item_filter', 10, 2 );
gtm4wp_eec_order_item filter
The same idea for the items of an order. Return false to keep a line out of the purchase event. It defaults to true.
function my_purchase_item_filter( bool $item_included, $order_item ): bool {
return false; // false hides the product from GTM reporting
}
add_filter( 'gtm4wp_eec_order_item', 'my_purchase_item_filter', 10, 2 );
gtm4wp_eec_order_data filter
Filters the order level data of the purchase event, such as the transaction ID, the value and the shipping and tax amounts. The callback receives the order data array and the WC_Order object.
gtm4wp_eec_item_affiliation filter
New in 2.0.0. Sets the GA4 affiliation field of an item, which is empty by default. The callback receives the current value, the product object and $attributes_used_for. It is useful on a marketplace or a multi vendor shop where each product belongs to a different seller.
gtm4wp_purchase_datalayer filter
Filters the whole data layer of the purchase event before it is written to the order received page. The callback receives the data layer array and the WC_Order object.
gtm4wp_woocommerce_datalayer_on_pageload filter
Filters the WooCommerce part of the data layer that is written on page load, before it is merged into the rest of the data layer.
gtm4wp_purchase_trackable_statuses filter
New in 2.0.0. Sets which order statuses make an order eligible for the purchase event. The callback receives an array of status slugs without the wc- prefix and the WC_Order being evaluated. Use it when your shop completes orders through a status the plugin does not treat as a sale by default.
function my_statuses( array $statuses, $order ): array {
$statuses[] = 'my-custom-status';
return $statuses;
}
add_filter( 'gtm4wp_purchase_trackable_statuses', 'my_statuses', 10, 2 );
Easy Digital Downloads e-commerce data
New in 2.1.0. The Easy Digital Downloads integration has its own versions of the WooCommerce filters above. gtm4wp_eec_item_with_source and gtm4wp_eec_item_affiliation run on Easy Digital Downloads items too. There the source object is the cart item array or the order item, and $attributes_used_for is one of productdetail, productlist, addtocartsingle, cart, checkout and purchase.
gtm4wp_eec_edd_cart_item: returnfalseto keep a cart item out of the reporting. Receivestrueand the cart item array.gtm4wp_eec_edd_order_item: the same for the items of an order. Receivestrueand the order item object.gtm4wp_eec_edd_order_data: filters the order data array. Receives the array and the order object.gtm4wp_edd_purchase_datalayer: filters the whole data layer of the purchase event. Receives the data layer array and the order object.gtm4wp_edd_datalayer_on_pageload: filters the Easy Digital Downloads part of the data layer written on page load.gtm4wp_edd_purchase_trackable_statuses: the order statuses that make an order eligible for the purchase event. Receives an array of status slugs and the order object.gtm4wp_edd_order_phone: the phone number of the buyer, used for enhanced conversions and the order data. Receives the number found in the order (or an empty string) and the order object. Return an empty string to leave the phone number out.
Google Data Manager
New in 2.1.0. These filters belong to the Google Data Manager integration, which is experimental, so treat their shape as subject to change.
gtm4wp_gdm_destinations: the list of destinations used at runtime. Each row is an array with the keyslabel,service_account,type,property_idandmeasurement_id. Rows you add or change are validated again, and an invalid row is dropped, never sent.gtm4wp_gdm_order_consent: the consent state stored with an order. Receives the state read from the visitor’s consent cookie (ornull) and the order: theWC_Orderobject on WooCommerce, the order ID on Easy Digital Downloads. Use it with a consent tool that keeps the choice inside your Google Tag Manager container. Returningnullstores nothing, which means unknown, not denied.gtm4wp_gdm_refund_event: one refund event, exactly as it would be sent. Also receives the refund description, the refund object and the order object of the store. Return an empty array to cancel the send. Anything else is validated like the built-in event before it leaves the site.gtm4wp_google_service_account_in_use: returntrueto stop a Google service account from being deleted while your code still uses it. Receivesfalseand the ID of the account.
Script placement filters
Each frontend feature loads its own JavaScript file, and every one of them is placed in the footer by default. The filters below all work the same way: they receive a single boolean and return one. Return false to move that script into the <head> section instead.
add_filter( 'gtm4wp_vimeo', function( bool $in_footer ): bool {
return false; // load in the <head> instead of the footer
} );
These filters run while scripts are enqueued, so register them no later than the wp_enqueue_scripts action.
The media trackers each have a filter of their own:
gtm4wp_vimeogtm4wp_soundcloudgtm4wp_spotifygtm4wp_twitchgtm4wp_wistiagtm4wp_dailymotiongtm4wp_mixcloudgtm4wp_videopressgtm4wp_jwplayergtm4wp_cloudflarestreamgtm4wp_html5media
Four more follow the same pattern for other features:
gtm4wp_integrate-wpcf7for the Contact Form 7 trackergtm4wp_event-form-movefor the form field interaction trackergtm4wp_integrate-woocommerce-track-enhanced-ecommercefor the WooCommerce trackergtm4wp_integrate-edd-track-ecommercefor the Easy Digital Downloads tracker (new in 2.1.0)
GTM4WP 1.x: the WooCommerce script was placed in the <head> by default and the filter moved it to the footer, so the meaning of the return value was the other way round for that one hook.
gtm4wp_media_sdk_blocked filter
New in 2.0.0. Return true to stop GTM4WP from loading the JavaScript API of any media player from the player’s own servers. Players that are already on the page are still tracked. To block a single player, dequeue that tracker’s script instead.
Extending the plugin
gtm4wp_register_modules action
New in 2.0.0. Version 2.0.0 is built from modules, and this action lets a third party plugin add one of its own. The callback receives the module registry.
add_action( 'gtm4wp_register_modules', function( $registry ) {
$registry->add( new My_Module() );
} );
A module supplies its own option defaults and frontend hooks, so this is the way to add a feature that behaves like a built-in one instead of hooking the data layer from outside. The registry is built while the plugin boots on plugins_loaded, so register from a file that loads before that, such as a must use plugin or the main file of your own plugin.
gtm4wp_admin_page_capability filter
Sets the capability a user needs to see and manage the GTM4WP settings page. It defaults to manage_options. Use it to open the settings screen to a role such as a marketing manager without granting full administrator rights.
add_filter( 'gtm4wp_admin_page_capability', function( string $capability ): string {
return 'edit_others_posts';
} );
The capability you return is enforced on the settings screen and its REST routes, on the Google service account and Data Manager routes, and on every ability the plugin registers for AI assistants and MCP. Be careful which role you open it to: whoever can reach the settings can change the container that loads on every page of the site.
Return a capability name. Anything else, such as an empty string or null, is reported with a _doing_it_wrong() notice and the default manage_options applies. Return 'do_not_allow' to deny every user.
GTM4WP 2.0.x: a return value that is not a capability name locks every user out of the settings, administrators included, without a notice.
gtm4wp_admin_doc_url filter
New in 2.0.0. Filters the link behind each help link of the settings screen. It receives the full URL, the documentation path and the anchor (an option key, or an empty string for a section link). A third party module can point its own links at its own site. Return an empty string to remove the link; only http and https addresses are kept.
gtm4wp_abilities_enabled and gtm4wp_abilities_allow_write filters
New in 2.1.0. Control the abilities GTM4WP registers for AI assistants and MCP. Return false from gtm4wp_abilities_enabled to register none. Return false from gtm4wp_abilities_allow_write to keep only the read-only abilities: the ones that change settings or contact Google are then not registered and refuse to run.
gtm4wp_site_health_info filter
New in 2.1.0. Filters the rows of the plugin’s Site Health Info section. It receives the rows and the options service. Use it for code that has something to report but no module of its own. The section is pasted into public support threads, so add states and counts only, never keys or personal data.
Removed hooks
The following hooks are no longer applied. A callback still attached to one of them simply never runs.
Removed in 2.1.0:
gtm4wp_youtube, together with the YouTube tracker. Use the built-in YouTube Video trigger of Google Tag Manager.
Removed in 2.0.0:
gtm4wp_scroller-enabled, together with the scroll tracking feature it belonged to. Google Tag Manager has a built-in scroll depth trigger that replaces it.gtm4wp_integrate-woocommerce-track-classic-ecommerce, together with classic e-commerce tracking. Use the GA4 e-commerce tracking and itsgtm4wp_eec_*filters instead.gtm4wp_add_global_vars, replaced bygtm4wp_add_global_vars_arrayas described above.
Other changes for developers in 2.1.0
gtm4wp_datalayer_push()returnsfalsewhen its$js_beforeor$js_afterargument is not a string, as documented. GTM4WP 2.0.x: such an argument was printed into the page asArray.- The
$gtp4wp_plugin_url,$gtp4wp_plugin_basenameand$gtp4wp_script_pathglobals, deprecated in 2.0.0, are removed. Useplugin_dir_url( GTM4WP_PLUGIN_FILE ),plugin_basename( GTM4WP_PLUGIN_FILE )andplugin_dir_url( GTM4WP_PLUGIN_FILE ) . 'build/'instead.
Related pages
See also hard coding container parameters in wp-config.php, setting up Google Tag Manager environments, making a theme compatible with e-commerce tracking, the Site Health report and AI assistants and MCP.

