Blog

← All articles

How to Track AJAX Form Submissions in Google Tag Manager

TL;DR: Standard GTM Form Submission triggers don't work with AJAX forms because the page doesn't reload. Instead, use a Click — All Elements trigger on the submit button, or an Element Visibility trigger on the success message. GTM Event Helper detects AJAX forms and generates the correct trigger type automatically.

You set up a GTM Form Submission trigger. It works on your test page. You publish. Then your client reports that form completions in GA4 are zero. The problem? The form uses AJAX — and GTM's built-in Form Submission trigger doesn't detect AJAX submits.

This article explains why traditional form tracking fails with AJAX, and three reliable alternatives that work.

Why don't standard form triggers work with AJAX?

GTM's Form Submission trigger listens for the browser's native submit event. When a traditional form submits, the browser navigates to the form's action URL — the page reloads or redirects. GTM intercepts this event, fires the trigger, and the tag executes before the page unloads.

AJAX forms break this because:

GTM's Form Submission trigger may fire when the user clicks "Submit," but the "Check Validation" option (which waits for the form to actually submit) will often fail because no real browser navigation occurs.

How do I track AJAX forms with a button click trigger?

The simplest approach: instead of tracking the form submission event, track the click on the submit button. This works because the user must click the button regardless of how the form submits its data.

  1. Find the submit button's CSS selector (e.g., form.contact-form button[type="submit"])
  2. Create a Click - All Elements trigger
  3. Set condition: Click Element matches CSS selector form.contact-form button[type="submit"]

Pros

Cons

For most use cases, tracking the button click is good enough. If your form has client-side validation, invalid submissions are a small percentage and can be filtered in GA4.

How do I use Element Visibility to track form success?

If the form shows a success message after submission (like "Thank you, we'll be in touch!"), you can track when that message becomes visible. This is the most accurate approach because it only fires when the form actually succeeds.

  1. Identify the success message element (e.g., .form-success-message or #thank-you)
  2. Create an Element Visibility trigger in GTM
  3. Selection method: CSS Selector
  4. Enter the selector for the success message
  5. Check "Observe DOM changes" — this is critical because the element appears dynamically

Pros

Cons

How do I use dataLayer.push() for AJAX form tracking?

If you have access to the site's source code (or a developer who can help), the most reliable approach is to push a custom event to GTM's dataLayer when the AJAX call succeeds:

// In your form's AJAX success callback:
fetch('/api/submit', { method: 'POST', body: formData })
  .then(response => {
    if (response.ok) {
      dataLayer.push({
        event: 'form_submit',
        form_name: 'contact',
        form_location: 'footer'
      });
    }
  });

Then create a Custom Event trigger in GTM that fires on form_submit.

Pros

Cons

Which AJAX form tracking method should I choose?

Here's a decision tree:

  1. Can you modify site code? → Use Method 3 (dataLayer.push). It's the gold standard.
  2. Does the form show a success message? → Use Method 2 (Element Visibility). Most accurate without code changes.
  3. Neither? → Use Method 1 (Form Button Click). It's pragmatic and works in 90%+ of cases.

How do popular frameworks handle AJAX form submissions?

React / Next.js

Forms often use controlled components with onSubmit handlers. The submit button may be a <button> inside a <form> or a standalone element calling a function. Check if the button has type="submit" — React forms sometimes use type="button" with an onClick handler.

HubSpot Forms

HubSpot embeds forms in iframes. GTM triggers don't fire inside cross-origin iframes. Options: use HubSpot's built-in analytics, or listen for the message event that HubSpot posts to the parent window when a form submits.

Gravity Forms (WordPress)

Gravity Forms with AJAX enabled submit via jQuery.ajax(). After successful submission, the form wrapper gets replaced with a .gform_confirmation_message element. Use Method 2 (Element Visibility) with the .gform_confirmation_message selector.

Formspree / Formsubmit.co

These services handle the AJAX call and either redirect to a thank-you page or show an inline confirmation. Check the form's configuration to determine which behavior is active.

How do I capture form field values with AJAX form tracking?

Tracking that a form was submitted is useful — but knowing what the user submitted is often more valuable. You might want to capture the selected plan, the lead source dropdown value, or a campaign field for attribution.

Reading Input Values at Submit Time

Create a Custom JavaScript variable in GTM that reads the field value when the trigger fires:

// GTM Custom JavaScript Variable: "Form - Selected Plan"
function() {
  var el = document.querySelector('select[name="plan"]');
  return el ? el.value : '(not set)';
}

For text inputs, the approach is identical:

// GTM Custom JavaScript Variable: "Form - Company Name"
function() {
  var el = document.querySelector('input[name="company"]');
  return el ? el.value : '(not set)';
}

Then pass these variables as event parameters in your GA4 Event tag.

Data Layer Variables for Complex Forms

If you're using Method 3 (dataLayer.push), include field values directly in the push:

dataLayer.push({
  event: 'form_submit',
  form_name: 'pricing_request',
  selected_plan: document.querySelector('select[name="plan"]').value,
  form_source: document.querySelector('input[name="utm_source"]').value
});

Then create Data Layer Variables in GTM for selected_plan and form_source — no custom JavaScript needed.

Privacy: Never Send PII to GA4

GA4's Terms of Service prohibit sending Personally Identifiable Information. Never capture email addresses, phone numbers, full names, or any data that identifies a specific person. If you need to track that a field was filled (for funnel analysis), send a boolean or hash instead:

// Send whether email was provided, not the actual email
email_provided: document.querySelector('input[name="email"]').value ? 'yes' : 'no'

How do I track multi-step AJAX forms?

Multi-step forms (wizards, checkout flows, onboarding sequences) require tracking each step as a separate event. Without this, you can't identify where users abandon the process.

Event Naming Convention

Use a consistent naming pattern that GA4 can recognize as a funnel:

StepEvent NameParameters
Step 1: Contact Infoform_stepstep_number: 1, step_name: 'contact_info'
Step 2: Company Detailsform_stepstep_number: 2, step_name: 'company_details'
Step 3: Plan Selectionform_stepstep_number: 3, step_name: 'plan_selection'
Submissionform_submitform_name: 'signup', total_steps: 3

Implementation Approach

For each step transition (usually a "Next" button click), fire a separate trigger. If the form uses AJAX to validate each step before advancing, use the same methods as for full form submission — track the button click or the appearance of the next step's content.

// dataLayer push on each step completion
dataLayer.push({
  event: 'form_step',
  step_number: 2,
  step_name: 'company_details',
  form_name: 'signup'
});

Funnel Visualization in GA4

Once your step events flow into GA4, build a Funnel Exploration report: go to Explore → Funnel Exploration, add each form_step event as a step (filtered by step_number), and end with form_submit. This shows the exact drop-off rate per step — invaluable for optimizing long forms.

How do I handle AJAX forms that redirect to a thank-you page?

Some AJAX forms submit data in the background but then redirect the user to a confirmation page via JavaScript. This is a hybrid pattern — the submission is AJAX, but the result looks like a traditional redirect.

The Redirect Pattern

// Common pattern: AJAX submit, then redirect
fetch('/api/submit', { method: 'POST', body: formData })
  .then(response => response.json())
  .then(data => {
    // Redirect after successful submission
    window.location.href = '/thank-you?ref=' + data.referenceId;
  });

Tracking Options

  1. Page View trigger on the thank-you URL: Create a trigger where Page Path contains /thank-you or equals /success. This is the simplest approach and the most reliable for this pattern.
  2. Capture form data before redirect: If the redirect happens too fast for a dataLayer event to fire, use the button click method (Method 1) to capture the submission intent before the redirect occurs.
  3. URL parameters on the thank-you page: If the redirect URL includes parameters (like ?plan=pro&source=homepage), read them with GTM's built-in URL variables to pass rich data to your GA4 event.

Which Approach to Choose

If the thank-you page URL is consistent and predictable, the Page View trigger is the simplest solution. If you need to capture data from the form itself (not just "a submission happened"), combine the Page View trigger with URL parameters or use Method 1 (button click) as a complementary event.

GTM Event Helper detects whether a form uses inline success messages or redirects, and suggests the appropriate trigger type — Element Visibility for inline confirmations, Page View for redirects, or Click for cases where neither is clear.

How do I track Shopify and WooCommerce AJAX forms?

E-commerce platforms use AJAX heavily for cart interactions, checkout steps, and payment processing. Each platform has specific selectors and behaviors you need to account for.

Shopify AJAX Cart and Product Forms

Shopify's default themes submit "Add to Cart" via AJAX using the Cart API (/cart/add.js). The page doesn't reload — items are added in the background and a cart drawer or notification appears.

For Shopify checkout, note that the checkout page is hosted on checkout.shopify.com — a different domain. GTM containers on your main domain won't fire there unless you use Shopify's built-in checkout extensibility or a server-side approach.

WooCommerce Checkout and Cart

WooCommerce uses jQuery AJAX for checkout processing and cart updates. Key selectors:

WooCommerce fires jQuery events like added_to_cart and updated_cart_totals. You can listen for these with a Custom HTML tag that pushes to the dataLayer:

<script>
jQuery(document.body).on('added_to_cart', function(e, fragments, hash, button) {
  dataLayer.push({
    event: 'add_to_cart',
    product_name: button.closest('.product').find('.product-title').text()
  });
});
</script>

Third-Party Payment Widgets

Stripe Elements, PayPal buttons, Apple Pay, and Google Pay all render inside iframes or shadow DOM. GTM triggers cannot reach inside cross-origin iframes. For these, you have two options:

  1. Track the click on the wrapper element around the payment button (outside the iframe)
  2. Use server-side tracking — fire a conversion event from your backend when payment succeeds, using GA4's Measurement Protocol

Server-side tracking is more reliable for payment conversions because it only fires when the payment actually processes, not when the user clicks a button that might fail.

How do I test AJAX form tracking in GTM?

Testing is different from click tracking because you need to actually submit the form:

  1. Enable Preview mode in GTM
  2. Fill out the form with test data
  3. Submit the form
  4. In Tag Assistant, look for your event (Click, Element Visibility, or Custom Event depending on your method)
  5. Verify the tag fired and the event parameters are correct

For Method 2 (Element Visibility), check that the success message appears and the trigger fires. If it doesn't, verify "Observe DOM changes" is enabled — without it, GTM only checks visibility on page load.

GTM Event Helper supports all three trigger types: Click, Form Submission, and AJAX Form Button Click.

Install GTM Event Helper

External Resources

Related Articles

← All articles · Home · Privacy Policy · Contact