Master form tracking with GTM and GA4

Updated: Monday, October 2, 2023

Read this carefully…

Forms are the biggest lead generators on the web, but they can be very difficult to track, especially if you’ve done very little HTML and CSS.

The complexity lies in tracking ONLY valid form submissions.

In this article, I explain 5 different methods.

Before adopting a particular method, remember to check its conditions of use by carrying out the suggested diagnostic.

Each method is associated with a level of difficulty (Easy, Medium or Difficult).

Start with the conditions of use of method 1, then let us guide you until you find the method that works for you.

Method 1: The “Form Submission” trigger

Method 1: Conditions of use

To check each condition, take a look at Method 1: Run the diagnosis.

flowchart TD
    A[Does the form sends the <b>'submit'</b> event ?]
    B[Is the event <b>gtm.formSubmit</b> triggered <b>ONLY</b>\n after a valid form submission?]
    C[Do you have access to variables \n to determine whether the form is valid or not?]
    D[<a href="#method-1-use-it">Use method 1</a>]
    E[<a href="#method-2-url-or-confirmation-page">Check method 2</a>]
    A -- YES --> B
    A -- NO --> E
    B -- NO --> C
    B -- YES --> D
    C -- YES --> D
    C -- NO --> E

Method 1: Run the diagnosis

Looking at your form’s HTML code, if you notice that the <form> tag contains the action attribute as in the example below, chances are your form is sending a submit event.

<form method="POST" action="https://some-url.com">
<!-- ... -->
</form>    

Similarly, if you notice that the HTML code of the form submit button is type="submit", your form should send a submit event.

<form>
    <input type="submit" value="Send"/>
    <!-- OR -->
    <button>Send</button> <!-- The <button> tag sends a submit event by default -->
    <!-- OR -->
    <button type="submit">Send</button>
</form>

Another way to find out is to monitor the gtm.formSubmit event in Google Tag Manager’s Debug Mode.

To do this, you’ll need to add the “Form Submission” trigger like this:

Form Submission trigger configuration in Google Tag Manager
Form Submission trigger configuration in Google Tag Manager

If the form sends a submit event, you should see this in Google Tag Manger’s Debug Mode:

Auto-event gtm.formSubmit linked to the Form Submission trigger
Auto-event gtm.formSubmit linked to the Form Submission trigger
Auto-event gtm.formSubmit linked to the Form Submission trigger

If you’ve mastered a little Javascript, to be totally sure that your form is sending a submit event, I invite you to install this code on your form page and see if an alert appears on the screen.

document.addEventListener("DOMContentLoaded", function() {
    document.querySelector("form").addEventListener("submit", function() {
        alert("Your form sends the submit event !!");
    });
});

This is what you should get if your form sends a submit event:

Alert proving that your form sends a submit event
Alert proving that your form sends a submit event

At this stage, you need to determine whether the form sends a submit event only when it is valid or each time it is submitted.

If you have access to variables that allow you to determine the validity of the form, you can check the Check Validation checkbox in the trigger.

Using a variable to check form validation
Using a variable to check form validation

If method 1 doesn’t work for your form, please proceed to method 2.

Method 1: Use it

As seen during diagnostic, you need to use the Form Submission trigger.

Form Submission trigger configuration in Google Tag Manager
Form Submission trigger configuration in Google Tag Manager

Check the Check Validation checkbox only if you have a variable that allows you to determine whether the form submission is valid.

Configure GA4 event

We’ll now configure GA4’s generate_lead event and link it to this trigger.

GA4 event tag configuration
GA4 event tag configuration

Method 1: Test it

We then test the configuration using Google Tag Manager’s debug mode.

GA4 event tag testing
GA4 event tag testing

We then check that the generate_lead event has been correctly sent to GA4 using the Debug View.

GA4 generate_lead event debugging
GA4 generate_lead generate_lead event debugging

Method 2: URL or Confirmation page

Method 2: Conditions of use

To check each condition, take a look at Method 2: Run the diagnosis.

flowchart TB
    A[Does the page reload \n after a form submission?]
    B[Does the URL or the page change <b>ONLY</b> \n after a valid form submission?]
    C[If the page does change, is the \n confirmation page unique?]
    D[After a form submission, does the <i>document.referrer</i> variable \n on the confirmation page \n indicates the URL of the form page?]
    E[Do you have access to variables \n to determine a valid form submission?]
    F[<a href="#method-2-use-it">Use method 2</a>]
    G[<a href="#method-3-ajax-listener">Check method 3</a>]
    H[<a href="#method-4-element-visibility-trigger">Check method 4</a>]
    I[Ask a developer to add a unique \n confirmation page for each form]
    A -- YES --> B
    A -- NO --> G
    B -- YES --> C
    B -- NO --> E
    C -- YES --> F
    C -- NO --> D
    D -- YES --> F
    D -- NO --> I
    E -- YES --> C
    E -- NO --> H

Method 2: Run the diagnosis

When you submit a valid form, the URL fragment may change (https://example.com/#form-sent) or you may be redirected to a confirmation page (https://example.com/confirmation-page).

On the confirmation page, you need to identify whether it’s unique by asking the developer or webmaster. And if it’s used for other forms or pages on the website, you may be able to differentiate it with the document.referrer variable.

This variable contains the previous URL that was visited. If it’s indeed the form page you want to track, then you can use method 2.

To determine this, submit the form, then right-click > Inspect on the confirmation page.

Then go to the Console tab, type document.referrer and press enter.

Display previous URL from the confirmation page
Display previous URL from the confirmation page

If the URL displayed is indeed that of your form page, you can continue with method 2.

Method 2: Use it

The URL fragment changes

Let’s say that when you submit a valid form, the #form-sent fragment is added to the URL.

We’re going to trigger our GA4 generate_lead event only when this fragment is in the URL.

To do this, we’ll first create a Fragment variable:

Variable to get the fragment in the URL
Variable to get the fragment in the URL

Then we create a trigger on page views that contain the #form-sent fragment:

Page view trigger containing the fragment
Page view trigger containing the fragment

We then link the GA4 event tag to this trigger:

GA4 event tag linked to the Page View trigger
GA4 event tag linked to the Page View trigger

Confirmation page

When you submit a valid form, you’re redirected to a single confirmation page.

We’ll create a Page View trigger with a condition on the page URL.

Page View trigger on the confirmation page
Page View trigger on the confirmation page

We then link the GA4 event tag to this trigger.

GA4 event tag linked to the confirmation page trigger
GA4 event tag linked to the confirmation page trigger

Method 2: Test it

The URL fragment changes

We then test the configuration in Google Tag Manager’s Debug Mode:

GA4 event debugging in GTM's Debug Mode
GA4 event debugging in GTM's Debug Mode

We then check that the generate_lead event has been correctly sent to GA4 using the Debug View.

GA4 generate_lead event debugging
GA4 generate_lead generate_lead event debugging

Confirmation page

We then test the configuration in Google Tag Manager’s Debug Mode:

GA4 event debugging in GTM's Debug Mode
GA4 event debugging in GTM's Debug Mode

We then check that the generate_lead event has been correctly sent to GA4 using the Debug View.

GA4 generate_lead event debugging
GA4 generate_lead generate_lead event debugging

Method 3: AJAX Listener

Method 3: Conditions of use

To check each condition, take a look at Method 3: Run the diagnosis.

flowchart TB
    A[Does the form submission send the <b>ajax_complete</b> \nevent after the installation of the code below?]
    B[Does variables in the Data Layer allow you to\n know whether the form submission is vqlid or not?]
    C[<a href="#method-3-use-it">Use method 3</a>]
    D[<a href="#method-4-element-visibility-trigger">Check method 4</a>]
    A -- YES --> B
    B -- YES --> C
    A -- NO --> D
    B -- NO --> D

Method 3: Run the diagnosis

First, you need to install the code below between the <head> tags on your form page.

You can ask a developer to set it up, or do it yourself via Google Tag Manager.

<script>
  window.dataLayer = window.dataLayer || [];
  var origin_open = XMLHttpRequest.prototype.open;
  var origin_send = XMLHttpRequest.prototype.send;
  var xhrRequestBody = "";

  XMLHttpRequest.prototype.send = function(body) {
    if(body instanceof FormData) {
      xhrRequestBody = Object.fromEntries(body);
    } else if(typeof body == "string" || body instanceof String) {
      body = decodeURIComponent(body);
      var a = body.split("&");
      var b = [];
      a.forEach(function(el) { return b.push(el.split("=")) });
      xhrRequestBody = Object.fromEntries(b);
    }

    origin_send.apply(this, arguments);

  };

  XMLHttpRequest.prototype.open = function() {
      this.addEventListener('loadend', function() {
          dataLayer.push({
            event: "ajax_complete",
            requestType: "XHR",
            requestBody: xhrRequestBody || "",
            location: document.location.href || "",
            path: document.location.pathname || "",
            fragment: document.location.hash || "",
            protocol: document.location.protocol || "",
            hostname: document.location.hostname || "",
            statusCode: this.status || "",
            statusText: this.statusText || "",
            responseType: this.responseType || "",
            readyState: this.readyState || ""
          });
        });
        origin_open.apply(this, arguments);
  };

  window.fetch = new Proxy(window.fetch, {
    apply: function (target, that, args) {
      var temp = target.apply(that, args);
      temp.then(function(res) {
        dataLayer.push({
              event: "ajax_complete",
              requestType: "Fetch",
              location: document.location.href || "",
              path: document.location.pathname || "",
              fragment: document.location.hash || "",
              protocol: document.location.protocol || "",
              hostname: document.location.hostname || "",
              statusCode: res.status || "",
              statusText: res.statusText || "",
              requestedURL: res.url || ""
          });
      });
      return temp;
    },
  });
  /*
  * v0.1.0
  * Created by Data Marketing School at http://www.data-marketing-school.com
  * Written by https://www.linkedin.com/in/lucasrollin/
  */
</script>

To do this via Google Tag Manager, create a custom HTML tag, then insert the above code.

Create an AJAX Listener in a custom HTML tag within GTM
Create an AJAX Listener in a custom HTML tag within GTM

Then check in Google Tag Manager for the ajax_complete event displayed when you submit your form.

The variables transmitted with the event should also enable you to differentiate between valid and invalid form submissions.

If this is the case, you can use method 3. If not, go to method 4.

Method 3: Use it

To trigger a tag following the ajax_complete event, we’ll create a custom event trigger and also create the statusText and statusCode variables in Google Tag Manager.

Create statusText and statusCode variables

These variables will be used in the next section when we configure the trigger.


Variable de couche de données (Data Layer) statusCode
Variable de couche de données (Data Layer) statusText
Data Layer variables statusCode and statusText

Create the ajax_complete custom event trigger

We now create the Custom event trigger on the ajax_complete event.


Trigger on ajax_complete event when statusCode is 200
Trigger on ajax_complete event when statusCode is 200

In this example, I’m checking that the statusCode is equal to 200. This check will depend on the statusCode sent by your form on a valid submission.

We then associate the GA4 event tag with this trigger:

GA4 event tag linked to the ajax_complete trigger
GA4 event tag linked to the ajax_complete trigger

Method 3: Test it

We then test the configuration with Google Tag Manager’s debug mode for both valid and invalid form submission.

GA4 event trigger debugging on a valid AJAX form submission
GA4 event trigger debugging on a valid AJAX form submission
GA4 event trigger debugging on an invalid AJAX form submission
GA4 event trigger debugging on an invalid AJAX form submission

Next, we check that the generate_lead event has been sent to GA4 using the Debug View.

GA4 generate_lead event debugging
GA4 generate_lead event debugging

Method 4: “Element Visibility” trigger

Method 4: Conditions of use

To check each condition, take a look at Method 4: Run the diagnosis.

flowchart TD
    A[Does the form submission \n displays a confirmation message?]
    B[Is the confirmation message already \n in the HTML code when the page loads?]
    C[<a href="#method-4-use-it">Use method 4</a>]
    D[<a href="#method-5-the-datalayer">Check method 5</a>]
    A -- YES --> B
    A -- NO --> D
    B -- YES --> C
    B -- NO --> D

Method 4: Run the diagnosis

Your form displays a confirmation message for valid submissions like this:

Conformation message when the form is submitted
Confirmation message when the form is submitted

And this message is already in the HTML code when the page loads, but simply hidden by the CSS, so you can use method 4.

<p id="valid" class="rounded-sm hidden text-slate-100 bg-green-700 px-3 py-3">The form has been sent.</p>

Here, the message contains the hidden class. It is hidden by default when the page is loaded and is displayed on a valid form submission.

Method 4: Use it

We’ll create an Element visibility trigger to detect when the <p> tag containing the message “The form has been sent.” becomes visible.

To do this, we specify the tag id (valid) and check the “Observe DOM changes” checkbox.

Configuration of Element Visibility trigger
Configuration of Element Visibility trigger

By default, the Element visibility trigger is triggered when the page is scrolled.

In the case of a form submission, there is no scroll, so it is necessary to check the “Observe DOM changes” checkbox.

We then link the GA4 event tag with this trigger:

GA4 event tag linked with the Element Visibility trigger
GA4 event tag linked with the Element Visibility trigger

Method 4: Test it

We then test the configuration with Google Tag Manager’s debug mode for both valid and invalid form submission.

Debug GA4 event tag trigger when the confirmation message appears
Debug GA4 event tag trigger when the confirmation message appears

Next, we check that the generate_lead event has been sent to GA4 using the Debug View.

GA4 generate_lead event debugging
GA4 generate_lead event debugging

Method 5: The dataLayer

Method 5: Conditions of use

flowchart TD
    A[Can you get help from a developer?]
    B[<a href="#method-5-use-it">Use method 5</a>]
    C[Rest in Peace]
    A -- YES --> B
    A -- NO --> C

Method 5: Use it

Ask a developer to trigger this code when a valid form submission happens:

window.dataLayer = window.dataLayer || [];
dataLayer.push({
    event: "form_sent"
});

You can modify the event name and add additional parameters if you wish to collect more information on the form.

Once your developer has implemented the code, all that’s left to do is set up a custom event trigger on the form_sent event or whatever event you want to use.

Custom event trigger on form_sent data layer event
Custom event trigger on form_sent data layer event

Then, we link it to the GA4 event tag:

Balise GA4 associée au déclencheur sur l'événement form_sent
Balise GA4 associée au déclencheur sur l'événement form_sent

Method 5: Test it

We then test the configuration with Google Tag Manager’s debug mode for both valid and invalid form submission.

Debug of GA4 event tag on form_sent event
Debug of GA4 event tag on form_sent event

Next, we check that the generate_lead event has been sent to GA4 using the Debug View.

GA4 generate_lead event debugging
GA4 generate_lead event debugging