WordPress
WordPress is the most common CMS platform you’ll implement GTM on. The implementation is simpler than ecommerce platforms — you’re primarily tracking content interactions, not transactions — but there are enough WordPress-specific patterns (how data is structured, how scripts should be enqueued, how form plugins work) that a dedicated guide is warranted.
Plugin options
Section titled “Plugin options”Before writing custom code, evaluate these plugin options:
Google Tag Manager for WordPress (GTM4WP) by Thomas Geiger — the gold standard. Handles GTM installation correctly, outputs a comprehensive dataLayer with page type, post categories, author data, and WooCommerce ecommerce events if WooCommerce is active. Recommended for most WordPress sites.
Google Site Kit — Google’s official plugin. Installs GA4 directly (without GTM). If your analytics setup needs GTM for multi-tool tracking or custom event logic, Site Kit’s GA4 direct integration bypasses GTM entirely.
Manual implementation — what this guide covers. More control, no plugin dependency, appropriate when you need custom dataLayer structure that plugins don’t support.
Manual GTM installation
Section titled “Manual GTM installation”Add GTM via functions.php in your child theme, or better, a custom plugin:
<?php/** * Plugin Name: Site Analytics * Description: GTM and dataLayer implementation * Version: 1.0.0 */
define('GTM_ID', 'GTM-XXXXXXX');
// Head snippet — priority 1 to load earlyfunction analytics_gtm_head() { ?> <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','<?php echo esc_js(GTM_ID); ?>');</script> <?php}add_action('wp_head', 'analytics_gtm_head', 1);
// Body noscript — requires wp_body_open support in themefunction analytics_gtm_body() { ?> <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=<?php echo esc_attr(GTM_ID); ?>" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> <?php}add_action('wp_body_open', 'analytics_gtm_body', 1);DataLayer initialization with page context
Section titled “DataLayer initialization with page context”Push page-level data before GTM loads so it’s available when the gtm.js event fires.
function analytics_datalayer_init() { global $wp_query;
$post_type = get_post_type(); $page_type = 'other';
if (is_front_page()) $page_type = 'homepage'; elseif (is_single()) $page_type = 'article'; elseif (is_page()) $page_type = 'page'; elseif (is_category()) $page_type = 'category'; elseif (is_tag()) $page_type = 'tag'; elseif (is_archive()) $page_type = 'archive'; elseif (is_search()) $page_type = 'search'; elseif (is_404()) $page_type = '404';
$data = [ 'page_type' => $page_type, 'post_type' => $post_type ?: 'unknown', 'site_name' => get_bloginfo('name'), ];
// Add post-specific data on single posts/pages if (is_singular()) { $post = get_queried_object(); $categories = get_the_category($post->ID); $primary_category = !empty($categories) ? $categories[0]->name : '';
$data['content_id'] = (string) $post->ID; $data['content_title'] = $post->post_title; $data['content_author'] = get_the_author_meta('display_name', $post->post_author); $data['content_category'] = $primary_category; $data['content_date'] = get_the_date('Y-m-d', $post); }
// Add user data if logged in if (is_user_logged_in()) { $current_user = wp_get_current_user(); $data['user_type'] = 'logged_in'; $data['user_role'] = implode(',', $current_user->roles); } else { $data['user_type'] = 'guest'; }
?> <script> window.dataLayer = window.dataLayer || []; dataLayer.push(<?php echo wp_json_encode($data); ?>); </script> <?php}add_action('wp_head', 'analytics_datalayer_init', 0); // Priority 0 — before GTM snippetContact Form 7 integration
Section titled “Contact Form 7 integration”Contact Form 7 fires a JavaScript event wpcf7mailsent on successful submission. Listen to it and push to the dataLayer.
// Add to plugin or functions.phpfunction analytics_cf7_tracking() { // Only enqueue on pages with CF7 forms if (!function_exists('wpcf7')) return; ?> <script> document.addEventListener('wpcf7mailsent', function(event) { var formId = event.detail.contactFormId; var formTitle = event.detail.inputs.find(function(i) { return i.name === '_wpcf7_unit_tag'; });
window.dataLayer = window.dataLayer || []; dataLayer.push({ event: 'form_submit', form_id: String(formId), form_name: 'cf7_' + formId, form_type: 'contact_form_7' }); }, false); </script> <?php}add_action('wp_footer', 'analytics_cf7_tracking');Gravity Forms integration
Section titled “Gravity Forms integration”Gravity Forms provides a PHP hook that fires after successful submission:
// Gravity Forms — fires server-side after submissionfunction analytics_gf_tracking($entry, $form) { // Output a dataLayer push script in the footer add_action('wp_footer', function() use ($entry, $form) { ?> <script> window.dataLayer = window.dataLayer || []; dataLayer.push({ event: 'form_submit', form_id: '<?php echo esc_js($form['id']); ?>', form_name: '<?php echo esc_js($form['title']); ?>', form_type: 'gravity_forms' }); </script> <?php });}add_action('gform_after_submission', 'analytics_gf_tracking', 10, 2);User login tracking
Section titled “User login tracking”function analytics_login_tracking($user_login, $user) { // Store login event for next page load via a transient set_transient('analytics_login_' . $user->ID, 1, 60);}add_action('wp_login', 'analytics_login_tracking', 10, 2);
function analytics_output_login_event() { if (!is_user_logged_in()) return;
$user_id = get_current_user_id(); $login_event = get_transient('analytics_login_' . $user_id);
if (!$login_event) return;
delete_transient('analytics_login_' . $user_id);
?> <script> window.dataLayer = window.dataLayer || []; dataLayer.push({ event: 'login', method: 'wordpress' }); </script> <?php}add_action('wp_footer', 'analytics_output_login_event');Enqueueing scripts properly
Section titled “Enqueueing scripts properly”Always use wp_enqueue_script for external JavaScript files. For inline dataLayer pushes that contain PHP data, use the wp_head / wp_footer action hooks.
// Enqueue external tracking helperswp_enqueue_script( 'site-analytics', plugin_dir_url(__FILE__) . 'js/analytics.js', [], // no dependencies '1.0.0', true // load in footer);
// Pass PHP data to JavaScriptwp_localize_script('site-analytics', 'siteAnalyticsData', [ 'pageType' => $page_type, 'ajaxUrl' => admin_url('admin-ajax.php'),]);Common mistakes
Section titled “Common mistakes”Using echo to output JavaScript without sanitization. Any PHP value output into a <script> tag must be sanitized. Use esc_js() for individual string values, wp_json_encode() for arrays and objects.
Adding GTM from a section that not every page uses. If your GTM code is in a template file that’s only included on certain page types, you’ll miss tracking on other pages. The GTM snippet must be in your root layout — typically header.php or via wp_head hook.
CF7 events firing on page load. Some WordPress setups output inline scripts from shortcodes. Make sure CF7 tracking only fires on the wpcf7mailsent event — not on page render.