Tracking form submissions in GA4 (the reliable way)
Form tracking breaks constantly because most setups listen for a button click instead of a real submission. Here is how to fire a clean generate_lead event that actually maps to submitted forms.
The reliable way to track a form submission is to fire the event when the submission succeeds — not when the submit button is clicked. Click tracking counts validation failures, double-clicks, and abandoned attempts as conversions, which is why so many sites report more leads than their CRM has ever seen. The fix is to hook the success state: a thank-you page, a confirmation message, or an explicit dataLayer push from your form handler.
Why click tracking over-counts
A click on a submit button means someone tried. It does not mean anything was recorded.
Between the click and a real lead sit: client-side validation failures, server-side rejections, duplicate submissions from impatient double-clicks, spam blocked by a filter, and network errors. Every one of those becomes a conversion if you trigger on the click.
The gap is not small. Forms with several required fields routinely see a large share of submit clicks fail validation on the first attempt, and each retry is another click. If your GA4 lead count is meaningfully above your CRM's, this is almost always why.
The four approaches, ranked
1. A dataLayer push from your form handler — best
If you control the code, push the event at the point in your JavaScript where the submission is confirmed successful:
fetch('/api/contact', { method: 'POST', body: data })
.then(function (res) {
if (!res.ok) throw new Error('submit failed');
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'generate_lead',
form_name: 'contact',
form_location: 'footer'
});
});
This is unambiguous, survives redesigns, and lets you attach useful parameters. It requires a developer, which is the only reason it is not universal.
2. A thank-you page trigger — reliable and simple
If successful submission redirects to a dedicated URL, trigger on that pageview. Nothing reaches that page without succeeding, so the count is honest.
Two cautions: the page must not be reachable any other way — no direct links, no bookmarks in your own navigation — and refreshes will re-count unless you guard for it.
3. GTM's form submission trigger — convenient, inconsistent
GTM's built-in trigger listens for the browser's form submit event. It works on traditional HTML forms with a genuine submit and full page navigation.
It does not work reliably on forms that submit via AJAX and never navigate, forms that preventDefault, forms rendered by a framework that intercepts submission, or forms inside an iframe. That covers most modern form builders — which is why this trigger has a reputation for firing unpredictably.
Use it if your form is a plain HTML POST. Verify carefully if it is anything else.
4. Element visibility on the success message — the fallback
When the form submits via AJAX and shows an inline confirmation, trigger on the visibility of that confirmation element. It only appears on success, so the signal is correct.
The weakness is coupling to markup: a CSS class or ID rename silently breaks it. If you use this approach, note the selector in your tracking plan and re-verify after any design change.
| Approach | Accuracy | Needs a developer | Breaks when |
|---|---|---|---|
| dataLayer push | Highest | Yes | The handler is rewritten |
| Thank-you page | High | No | Page becomes reachable directly |
| GTM form trigger | Variable | No | AJAX, frameworks, iframes |
| Success-message visibility | Good | No | Markup changes |
Embedded and third-party forms
Forms from HubSpot, Typeform, Calendly, Mailchimp and similar tools usually render in an iframe on another domain. You cannot read inside it, so no DOM-based approach works.
Options, in order of preference:
- Use the vendor's JavaScript callback. Most embeddable form tools expose an on-submit or on-success event you can subscribe to. This is the correct answer and is frequently overlooked.
- Use the vendor's redirect-on-success to a thank-you page you control.
- Send the conversion server-side from the vendor's webhook — the most accurate option, since it fires from the system that actually recorded the lead.
Do not track the click that opens the embed as a conversion. That is an intent signal, not a lead, and mislabelling it corrupts every downstream optimisation.
Use the recommended event name
GA4 defines generate_lead for this. Use it, with value and currency if you can estimate lead worth. Custom names like form_submit_contact collect fine and integrate with nothing.
Distinguish forms with parameters, not with names:
event: 'generate_lead'
form_name: 'contact' | 'demo_request' | 'newsletter'
form_location: 'footer' | 'pricing_page' | 'modal'
Then register form_name and form_location as custom dimensions so you can break leads down by both. Custom dimensions covers registration.
Do not put PII in the event
Never send the submitted email address, phone number, or name to GA4. It is prohibited, and it is the most common route by which properties end up contaminated — keeping PII out of GA4 covers the consequences.
If you need those values for Google Ads matching, that is Enhanced Conversions, which hashes them and sends them only to Ads. Keep those variables scoped to the Ads tag alone.
Verifying it
- Submit successfully and confirm exactly one event with correct parameters.
- Submit with a validation error deliberately — a missing required field. No event should fire. This is the test that catches click-based triggers, and it is the one people skip.
- Double-click submit. Still one event.
- Reconcile a week against your CRM or inbox. GA4 slightly lower is normal — consent, blockers. GA4 higher means you are counting attempts.
- Check spam. If your form gets bot submissions, they may be real submissions that should not count as leads. Filter them at the source rather than in reporting.
FAQ
Why does GA4 show more form submissions than leads in my CRM?
Almost always because the event fires on the submit click rather than on success, so validation failures and retries are counted. Test by submitting with a missing required field — if an event fires, that is your answer.
Why doesn't GTM's form submission trigger work on my form?
Because it listens for the browser's native submit event. Forms that submit via AJAX, call preventDefault, are managed by a framework, or live in an iframe never fire it.
How do I track a HubSpot or Typeform embed?
Use the vendor's JavaScript callback for submission success, redirect to a thank-you page you control, or fire the conversion server-side from their webhook. You cannot read inside the iframe.
Should I use generate_lead or a custom event name?
Use generate_lead. It is GA4's recommended event and integrates with reporting and Ads import; distinguish between forms with parameters rather than with separate event names.
Can I send the submitted email address to GA4 for matching?
No. That violates Google's terms. Use Enhanced Conversions for Google Ads matching, keeping the user-provided data scoped to the Ads tag only.
Check whether your forms' events are even reaching GA4 — the free tracking audit checks tag presence, dataLayer initialisation, and PII exposure on any URL.
See where your tracking stands
Run the same 13-check audit referenced in this post against any URL. No signup, results in seconds.