Google Tag Manager AJAX Form Tracking
Updated: December 8th, 2022
In this post, I’ll show you how to implement Google Tag Manager AJAX Form Tracking. This article is a part of a much larger and more comprehensive guide – Google Tag Manager Form Tracking.
A video tutorial
If you prefer video content, here’s a tutorial from my Youtube channel.
GA4 event tag
You are probably already familiar with the main Google Tag Manager concept: every interaction you want to track needs a tag and a trigger. If you want to track all form submissions with Google Analytics 4, you’ll need to create a Google Analytics Tag and a Trigger (rule) when a tag must fire.
Creating a tag is the easy part of this process. Let’s make one – you’ll need it in the next chapter of this blog post.
In this article, I presume that you have already installed Google Analytics 4 and already have the GA4 configuration tag. If not, read this blog post first.
After you have a GA4 config tag in your GTM container, it’s time to create a GA4 event tag that will be used in all of the examples of this article.
- Go to Tags
- Press New button
- In Tag Configuration choose Google Analytics: GA4 Event
- In the Configuration Tag field, select your current GA4 configuration tag. The event tag that we are creating will reuse some of the settings from the configuration tag (e.g. Measurement ID)
- In the Event Name field, I enter generate_lead
- Leave the Triggering part empty (for now) and click Save. We’ll get back to it later.
Let me quickly explain some of the choices. The reason why I used generate_lead as an event name is that it is in the list of GA4 Recommended events. But if you want, you can name it something else.
Custom HTML tag with the AJAX listener
If you are reading this blog post, your form is probably not supported by the built-in Form Submission trigger and is not redirecting users to a “thank you” page. It probably just refreshes itself and then displays “You have successfully filled in the form” message without the actual page refresh.
There’s a big chance that this form is using AJAX. I suggest skipping all the technical details here. The only thing here you should know is AJAX listener.
Bounteous have shared an awesome AJAX listener for GTM everyone can use for free. Here we’ll borrow their code to track form submissions. Copy the code below and paste it in the Custom HTML tag on Google Tag Manager:
<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>
Set that Custom HTML tag to fire on all pages.
Now, let’s check whether a form is built on AJAX:
- Enable (or refresh) Preview and Debug mode.
- Try submitting the form on your website (with no errors).
- Did the ajaxComplete event appear in the Preview and debug console?
- If yes, the form uses AJAX.
- If not, then read this blog post.
If your answer to the previous questions was Yes, let’s take a look at what we can do with that AJAX form. Click the ajaxComplete event in Preview and Debug mode, then expand the API call:
Looks difficult for a non-developer, right? But it’s easier than you think.
This is data was passed to the data layer after the successful submission of the form. Each line is a separate dataLayer data point that can be used as a dataLayer variable in GTM.
Now you should look for something that helps identify successful form submissions. Scroll down and look for “response”.
Let’s take a closer look at it. Can you see the message “Thanks for contacting us! We will be in touch with you shortly”? Bingo! We can use it as a trigger.
Data Layer Variable and Custom Event Trigger
First, let’s create a Data Layer variable in Google Tag Manager.
- Go to Variables
- Scroll down to the User-Defined variable and hit New
- Click Variable configuration and choose variable type – Data Layer Variable
- Enter Data Layer Variable Name – attributes.response.data.message
You’re probably guessing why I entered attributes.response.data.message as Data Layer Variable Name instead of just response. Let’s take a closer look at Data Layer in Preview and Debug mode.
In line 2, you see the event named ajaxComplete – that’s the same name that appears in Preview and Debug console’s left side. Then we see attributes which is an object containing various data points (key-value pairs). And the response is one of those keys.
Within that response (2), we see data (3), and within that, we see the message (4).
Think of this as accessing folders. First, you have to access the attributes, then you go to response, then you go do the data folder and then you access the message.
IMPORTANT: in your case, the structure of the data can be different, and parameters can be named differently. It will always start with attributes.response, but after that, things might differ in every form. It might be attributes.response.message or something like that. You will need to adapt.
Another example: let’s say you’re interested in Server data (from that very exact AJAX response). In that case, the Data Layer Variable’s Name should be attributes.headers.Server .
After we create the Data Layer variable in Google Tag Manager, let’s debug. Refresh Preview and Debug mode (by clicking the Preview button in the GTM interface).
Fill in the form and submit it. Click the most recent ajaxComplete event in Preview and Debug console, then navigate to the Variables tab and find the new variable dlv – attributes.response.data.message. If you did everything correctly, it should look like this:
That’s a message of the successfully submitted form. If the value of that variable is undefined, then you should start looking for mistakes. The most common ones are typos in the variable name or inaccurately defined variable’s path. Some people just try using response instead of attributes.response.data.message.
Now let’s create a trigger that fires when the event is ajaxComplete AND our new Data Layer variable contains “Thanks for contacting us”.
- Go to Triggers and click New
- Choose Trigger Type – Custom Event
- Enter Event name – ajaxComplete
- This trigger should fire on Some Custom Events.
- Define a condition when the trigger will fire – dlv – attributes.response.data.message contains Thanks for contacting us!
Let’s Test
- Assign this new trigger to the Google Analytics 4 Event Tag that you created at the beginning of this blog post.
- Open (or refresh) a Preview and Debug mode
- Then fill in the AJAX form and submit it. After successful submission, Google Analytics 4 Event Tag should fire (it will be displayed in Preview and Debug mode. You should also check Google Analytics 4 Debug View.
Things to keep in mind when tracking AJAX forms:
- The response of your form might look different so you should adjust your Data Layer Variable and Custom Event trigger.
- If developers change the response’s data, your trigger will fail. Inform developers about your GTM implementation.
- If the page contains more than one AJAX form, try looking for more information in Data Layer, which can help Google Tag Manager tell the difference between those forms.
If Google Tag Manager AJAX Form Tracking Doesn’t Work for You
I have created a very detailed Google Tag Manager form tracking guide that includes many different form tracking techniques. If this Google Tag Manager AJAX form tracking method didn’t work for you, check the other options mentioned here.
If you have questions, feel free to contact me via social media accounts or write a comment on this blog post.
