To track form abandonment in GA4, count the people who start your form and the people who actually send it, then look at the gap in a funnel exploration. GA4's enhanced measurement already sends form_start when a visitor first changes a field and moves on. For the finish, add a generate_lead event when your backend confirms the lead: in our test, enhanced measurement sent no form_submit at all when the page's own JavaScript handled the submit, which is how most modern forms work. Abandonment is then the share of people with form_start and no generate_lead, and an optional field event shows which field they stop at.
Key takeaways
- Turn on enhanced measurement's form interactions: it sends
form_startandform_submitwith the form's HTMLidasform_id. form_startandform_submitare reserved event names, so don't send your own events with those names.- In our 30 October 2026 test, a form sent with
fetchafterevent.preventDefault()gotform_startbut neverform_submit. Sendgenerate_leadafter a confirmed lead instead. - Build a closed funnel exploration from
form_starttogenerate_lead, filtered onform_id. Abandonment is 100% minus its completion rate. - GA4 tells you how many people left and where, never who. Send field names only, never what people typed.
What GA4 tracks on its own
Enhanced measurement has a Form interactions option. Turn it on in Admin, under Data collection and modification: Data streams, your web stream, Enhanced measurement, then the settings icon. If you use the Google tag, its automatic event detection must allow form events too; by default all event types are on (Google's enhanced measurement page, checked October 2026).
| Event | When Google says it fires | Parameters |
|---|---|---|
form_start | "the first time a user interacts with a form in a session" | form_id, form_name, form_destination |
form_submit | "when the user submits a form" | The same three, plus form_submit_text |
form_id is the form's HTML id attribute and form_name its name. Google notes that you can only use these parameters in reports after you create custom dimensions for them. Both event names are on Google's list of reserved event names, so a gtag('event', 'form_start') of your own is the wrong fix.
What we saw when we tested it
Google doesn't document how enhanced measurement detects forms, so on 30 October 2026 we loaded the gtag.js Google was serving into a test page with four forms, in Chromium, and recorded every GA4 request the page tried to send (none left the browser).
form_startwaits for the visitor to leave a changed field. Typing alone sent nothing; leaving the field did. The tag listens for the browser'schangeevent, not focus or keystrokes.- A plain HTML form sent
form_submit. The form posted and loaded its thank-you page, and the event went out on the way. - A form sent with JavaScript got no
form_submit. Its submit handler calledevent.preventDefault()and sent the data withfetch. The tag saw the submit, marked it as cancelled (gtm.formCanceled: truein the data layer) and sent nothing. - A form sent from a button's click handler got no
form_submiteither. There was no submit event to see. - A submit blocked by validation sent nothing. Neither did it count as a start, because no field had changed.
Older guides say form_submit fires on every attempt, failed ones included. That isn't what we saw for cancelled submits. What is still true: the tag only sees the browser's submit, so a plain form whose server then rejects the lead still counts as form_submit. Google can change gtag.js at any time, so check your own forms in DebugView.
Two more limits follow from how browsers work. Forms embedded in an iframe from another site (a Tally, Typeform or HubSpot embed, for example) are a separate document, so the tag on your page never sees their fields; use the builder's own analytics integration or the events its embed sends to your page. And enhanced measurement counts every form, search boxes and newsletter sign-ups included, which is why every step below filters on form_id.
The setup: three events
| Event | Sent by | Means |
|---|---|---|
form_start | Enhanced measurement | The visitor changed a field in this form |
form_field_complete | Your code (optional) | The visitor left a field with something in it |
generate_lead | Your code | Your backend confirmed the lead |
generate_lead is one of Google's recommended events ("when a lead has been generated (for example, through a form)"), with optional value, currency and lead_source parameters. For a plain HTML form that loads a thank-you page, enhanced measurement's form_submit can be the last step instead, but generate_lead on the thank-you page is the same signal for every form.
1. Give every form an id
Enhanced measurement reports the form's id as form_id. Without one, all your forms look alike. <form id="contact"> is enough.
2. Send generate_lead when the lead arrives
In a form sent with JavaScript, send it inside your submit handler, after the response says the lead was received:
// Inside the submit handler that sends the form with fetch:
const res = await fetch(form.action, {
method: 'POST',
body: new FormData(form),
headers: { accept: 'application/json' }
});
if (res.ok) {
gtag('event', 'generate_lead', { form_id: form.id }); // only once the lead has arrived
}
For a plain HTML form, send it from the thank-you page:
<!-- On the thank-you page of a plain HTML form -->
<script>
gtag('event', 'generate_lead', { form_id: 'contact' });
</script>
Then mark generate_lead as a key event: Admin, under Data display, Events, and the star next to it (Google's key events page). If people can reload the thank-you page, set the key event's counting method to once per session.
3. Send an event per field (optional)
This tells you which field people stop at. It sends one event per field per page view, with the field's name and never its value, and skips password and hidden fields.
// After your Google tag. One event per field per page view, with the field's name, never its value.
(() => {
const form = document.getElementById('contact'); // the id GA4 reports as form_id
const done = new Set();
form.addEventListener('focusout', (e) => {
const field = e.target;
if (!field.name || done.has(field.name)) return;
if (['password', 'hidden', 'submit', 'button'].includes(field.type)) return;
if (typeof field.value !== 'string' || !field.value.trim()) return;
done.add(field.name);
gtag('event', 'form_field_complete', { form_id: form.id, field_name: field.name });
});
})();
Never put what the visitor typed into an event. Google's policy is that no data Google could recognise as personally identifiable, such as email addresses or phone numbers, is sent to Analytics (Google's PII guidance). Event names can be up to 40 characters and parameter values up to 100 (collection limits).
4. Register the custom dimensions
In Admin, under Data display, open Custom definitions and create two event-scoped custom dimensions: one for the parameter form_id and one for field_name. Google says the data shows in reports after 24 to 48 hours, and a standard property can have 50 event-scoped custom dimensions (custom dimensions).
5. Check it in DebugView
Turn on debug mode for your own browser with Tag Assistant (or Tag Manager's Preview), then open Admin, Data display, DebugView (Google's DebugView page). Fill in the form: you should see form_start when you leave the first field you changed, one form_field_complete per field, and generate_lead once the lead is confirmed. For a JavaScript form, no form_submit is expected. If you use consent mode, accept analytics on your own banner first: Google says events don't appear in DebugView without that consent.
Build the abandonment funnel
- In Explore, start a Funnel exploration.
- Step 1, Started: event
form_start, with the parameterform_idexactly matchingcontact. - Step 2, Sent: event
generate_leadwith the sameform_idcondition. Leave it as "is indirectly followed by"; add "Within" 30 minutes if you only want people who finished in one sitting. - Leave Make open funnel off. A closed funnel only counts people who entered at step 1.
- Add Device category as the breakdown to compare mobile with desktop.
Step 2's completion rate is your completion rate; abandonment is 100% minus it. If 200 people started and 60 sent the form, 30% completed and 70% abandoned. Funnel explorations count users, not sessions, and a user enters a funnel only once in the date range, with their first sequence (Google's funnel exploration page). An open funnel isn't the same-session option: it lets people enter at any step.
For the field view, build a second funnel with one form_field_complete step per field, in the form's order (filter each on field_name), and generate_lead last. A funnel can have up to 10 steps. The biggest drop is the field to look at first. People who fill fields out of order fall out of a strict sequence, so a free-form exploration with Field name as rows and event count as the value is a useful second view. For what a normal rate looks like, and what to fix first, see our form abandonment guide.
The same setup in Google Tag Manager
Enhanced measurement is a setting on the web stream, so form_start works the same through Tag Manager's Google tag. For the other two events, push to the data layer from your page and let Tag Manager send them.
- In your submit handler, once the lead has arrived:
// In your submit handler, once the lead has arrived: window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'lead_sent', form_id: form.id }); - Create a Data Layer Variable named
form_id(and one namedfield_namefor the field events). - Create a Custom Event trigger with the event name
lead_sent. - Create a Google Analytics: GA4 Event tag with your measurement ID, the event name
generate_leadand an event parameterform_idset to the variable. Fire it on thelead_senttrigger. - For field events, add this as a Custom HTML tag fired on All Pages, then a Custom Event trigger for
form_field_completeand a GA4 Event tag that sendsform_field_completewithform_idandfield_name:<script> // Custom HTML tag, fired on All Pages: one dataLayer event per field per page view, // with the field's name, never its value. (function () { var done = {}; document.addEventListener('focusout', function (e) { var field = e.target; if (!field.form || !field.form.id || !field.name) return; if (/^(password|hidden|submit|button)$/.test(field.type)) return; if (!String(field.value || '').trim()) return; var key = field.form.id + ':' + field.name; if (done[key]) return; done[key] = true; window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'form_field_complete', form_id: field.form.id, field_name: field.name }); }); })(); </script> - Click Preview to test with Tag Assistant, then Submit and publish (Google's GA4 events in Tag Manager).
Tag Manager's built-in Form Submission trigger has the same problem as enhanced measurement. With Check Validation on, it fires only if the form is successfully sent, so a JavaScript form that cancels the submit never fires it; with it off, it fires on every attempt (form submission trigger). The data layer push after a confirmed lead is the reliable signal.
What GA4 can't tell you
- Who left. GA4 gives you counts, not people, and its policy rules out sending their email or phone. To reply to the person who left, the form itself has to save their contact details before they press Send, with a clear notice and, for EU and UK visitors, consent first (the form abandonment guide covers the law). OllieLeads does that for forms you build yourself (partial submissions): GA4 keeps giving you the rate and the field, and the left-early lead lands in your inbox.
- People who declined analytics. With consent mode, visitors who say no to analytics don't show up in DebugView, and your observed counts cover fewer people than visited. Compare rates over time rather than raw counts.
- Forms that send by GET. With
method="get", what the visitor typed ends up in the next page's URL (our test form put?email=...there), and GA4 records page URLs aspage_location. Use POST. Data redaction in the web stream's settings removes email addresses on a best-effort basis, as a backstop. - Whether the lead reached you.
generate_leadsays your backend answered OK. If the email notification never arrives, that is a delivery problem: see contact form not sending email.
FAQ
Does GA4 track form abandonment automatically?
Partly. Enhanced measurement sends form_start and form_submit, but there is no abandonment report, and in our test forms sent with JavaScript got no form_submit. Add generate_lead after a confirmed lead and compare the two in a funnel exploration.
Why is GA4 not recording form_submit?
The usual reasons: form interactions are off in enhanced measurement, the form's submit handler cancels the browser's submit and sends with fetch, the form is sent from a button click with no submit event, the form sits in an iframe from another site, or the visitor declined analytics under consent mode. Check in DebugView, and send generate_lead yourself.
Can I send my own form_start event?
No. form_start and form_submit are reserved event names in GA4. Use enhanced measurement's form_start, and give your own events other names, such as generate_lead and form_field_complete.
How do I see which field people abandon on?
Send a form_field_complete event with the field's name each time a visitor leaves a field with something in it, register field_name as a custom dimension, and build a funnel with one step per field. The biggest drop between steps is the field to fix first.
Can GA4 show me who abandoned my form?
No. GA4 reports counts, and Google's policy forbids sending email addresses, phone numbers or other personally identifiable information. To follow up with people who left, the form has to save their contact details itself, with their consent where the law requires it.
Published with SuperDuperSEO