This guide will help you set up Google Tag Manager to track successful form submissions in WP Forms that use AJAX. This step-by-step guide assumes that you are familiar with Google Tag Manager and have a basic understanding of the function of tags and triggers.
We also assume that you are familiar with WP Forms. However, knowledge of JavaScript or coding is not required so anyone can follow these steps.
We also assume that you’ve tried other methods such as using a CSS selector and are very frustrated that it didn’t work.
There is one very important caveat with this guide: The method described in this guide will track and aggregate submissions from every form created with WP Forms.
Always preview changes made to Google Tag Manager before publishing!
This guide consists of four major steps:
- Open the WP Forms plugin and check if “Enable AJAX form submission” is switched on.
- Create an AJAX listener script and use the “All Pages” trigger
- Create a Data Variable and input “attributes.response.success” in the field called “Data Layer Variable Name”
- Create a Custom Event trigger and input “ajaxComplete” in the field name called “Event name” and ensure that it is triggered only when your custom Data Variable contains “true”
- Create a tag and use your custom trigger as a condition
Ok, let’s do this!
1. Check WP Forms if “Enable AJAX form submission” is toggled
Open your WordPress admin interface and select WP Forms. Click on “Edit” under the name of your form.
Then, click on the “Settings” tab in the side menu on the left and select “General”. Scroll down and see whether “Enable AJAX form submission” is toggled on.
2. Create an AJAX listener script
Login to your Google Tag Manager and select the container that is deployed on your website.
Create a new tag and select Custom HTML as the tag configuration setting.
Then, simply paste the following script below and select All Pages as the firing trigger.
<script id="gtm-jq-ajax-listen" type="text/javascript"> (function() { 'use strict'; var $; var n = 0; init(); function init(n) { // Ensure jQuery is available before anything if (typeof jQuery !== 'undefined') { // Define our $ shortcut locally $ = jQuery; bindToAjax(); // Check for up to 10 seconds } else if (n < 20) { n++; setTimeout(init, 500); } } function bindToAjax() { $(document).bind('ajaxComplete', function(evt, jqXhr, opts) { // Create a fake a element for magically simple URL parsing var fullUrl = document.createElement('a'); fullUrl.href = opts.url; // IE9+ strips the leading slash from a.pathname because who wants to get home on time Friday anyways var pathname = fullUrl.pathname[0] === '/' ? fullUrl.pathname : '/' + fullUrl.pathname; // Manually remove the leading question mark, if there is one var queryString = fullUrl.search[0] === '?' ? fullUrl.search.slice(1) : fullUrl.search; // Turn our params and headers into objects for easier reference var queryParameters = objMap(queryString, '&', '=', true); var headers = objMap(jqXhr.getAllResponseHeaders(), '\n', ':'); // Blindly push to the dataLayer because this fires within GTM dataLayer.push({ 'event': 'ajaxComplete', 'attributes': { // Return empty strings to prevent accidental inheritance of old data 'type': opts.type || '', 'url': fullUrl.href || '', 'queryParameters': queryParameters, 'pathname': pathname || '', 'hostname': fullUrl.hostname || '', 'protocol': fullUrl.protocol || '', 'fragment': fullUrl.hash || '', 'statusCode': jqXhr.status || '', 'statusText': jqXhr.statusText || '', 'headers': headers, 'timestamp': evt.timeStamp || '', 'contentType': opts.contentType || '', // Defer to jQuery's handling of the response 'response': (jqXhr.responseJSON || jqXhr.responseXML || jqXhr.responseText || '') } }); }); } function objMap(data, delim, spl, decode) { var obj = {}; // If one of our parameters is missing, return an empty object if (!data || !delim || !spl) { return {}; } var arr = data.split(delim); var i; if (arr) { for (i = 0; i < arr.length; i++) { // If the decode flag is present, URL decode the set var item = decode ? decodeURIComponent(arr[i]) : arr[i]; var pair = item.split(spl); var key = trim_(pair[0]); var value = trim_(pair[1]); if (key && value) { obj[key] = value; } } } return obj; } // Basic .trim() polyfill function trim_(str) { if (str) { return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); } } })(); /* * v0.1.0 * Created by the Google Analytics consultants at http://www.lunametrics.com * Written by @notdanwilkerson * Documentation: http://www.lunametrics.com/blog/2015/08/27/ajax-event-listener-google-tag-manager/ * Licensed under the Creative Commons 4.0 Attribution Public License */ </script>
3. Create a new Data Layer Variable
In Google Tag Manager, select the Variables tab in the side menu. Scroll down to the section for User-Defined Variables and click on New.
Select Data Layer Variable as the Variable Type.
Under the Data Layer Variable Name field, input attributes.response.success
4. Create a custom trigger
Create a new custom trigger and select Custom Event as the Trigger Type.
Under the Event name field, input ajaxComplete.
Select Some Custom Events, which will display three input fields.
In the first field, which is a drop-down menu, find and select the custom data variable that you made earlier.
In the second field, select contains.
In the third field, write true.
5. Create a tag to track form submissions in GA4
Create a new tag and click on Tag Configuration.
Select Google Analytics and pick Google Analytics: GA4 Event
Input your GA4 Measurement ID and the name of your existing GA4 event (or create a new one).
The final step is to select your Custom Trigger.
Conclusion
Proper conversion tracking is essential for digital marketing. Without the ability to measure and optimize for specific campaigns, you wouldn’t be able to scale campaigns and drive revenue or leads.