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:
- They call
event.preventDefault()on the submit event, stopping the native navigation - They send data via
fetch()orXMLHttpRequestin the background - The page never reloads — it shows a success message dynamically
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.
- Find the submit button's CSS selector (e.g.,
form.contact-form button[type="submit"]) - Create a Click - All Elements trigger
- Set condition: Click Element matches CSS selector
form.contact-form button[type="submit"]
Pros
- Simple to set up
- Works with any AJAX framework (React, Vue, jQuery, plain fetch)
- No custom JavaScript needed
Cons
- Fires on every click, even if the form has validation errors
- Doesn't guarantee the form actually submitted successfully
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.
- Identify the success message element (e.g.,
.form-success-messageor#thank-you) - Create an Element Visibility trigger in GTM
- Selection method: CSS Selector
- Enter the selector for the success message
- Check "Observe DOM changes" — this is critical because the element appears dynamically
Pros
- Only fires on successful submissions
- Works with any framework
- No false positives from validation errors
Cons
- Requires a visible success element with a stable CSS selector
- If the form redirects to a thank-you page instead of showing an inline message, use a Page View trigger on the thank-you URL instead
- "Observe DOM changes" can have minor performance overhead
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
- Most accurate — fires exactly when submission succeeds
- Can include rich metadata (form name, fields, etc.)
- Works regardless of UI changes
Cons
- Requires code changes on the site
- Developer dependency
Which AJAX form tracking method should I choose?
Here's a decision tree:
- Can you modify site code? → Use Method 3 (dataLayer.push). It's the gold standard.
- Does the form show a success message? → Use Method 2 (Element Visibility). Most accurate without code changes.
- 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:
| Step | Event Name | Parameters |
|---|---|---|
| Step 1: Contact Info | form_step | step_number: 1, step_name: 'contact_info' |
| Step 2: Company Details | form_step | step_number: 2, step_name: 'company_details' |
| Step 3: Plan Selection | form_step | step_number: 3, step_name: 'plan_selection' |
| Submission | form_submit | form_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
- Page View trigger on the thank-you URL: Create a trigger where Page Path contains
/thank-youor equals/success. This is the simplest approach and the most reliable for this pattern. - 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.
- 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.
- Add to Cart button:
form[action*="/cart/add"] button[type="submit"] - Cart drawer open: Use Element Visibility on
.cart-drawer.is-openor[data-cart-drawer][aria-hidden="false"] - Quick buy buttons:
button.quick-add-btnorbutton[data-product-id]
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:
- Place Order button:
.woocommerce-checkout button[type="submit"]#place_order - Add to Cart (archive pages):
.add_to_cart_button - Add to Cart (single product):
.single_add_to_cart_button - Coupon apply:
button[name="apply_coupon"]
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:
- Track the click on the wrapper element around the payment button (outside the iframe)
- 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:
- Enable Preview mode in GTM
- Fill out the form with test data
- Submit the form
- In Tag Assistant, look for your event (Click, Element Visibility, or Custom Event depending on your method)
- 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 HelperExternal Resources
- Google: Set up form submission triggers in GTM
- Google: Element Visibility trigger in GTM
- Google: The data layer — GTM for developers