# Form abandonment tracking in GA4: a tested setup

_By Luka Brzin, Senior Product Marketing Manager_

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_start` and `form_submit` with the form's HTML `id` as `form_id`.
-   `form_start` and `form_submit` are reserved event names, so don't send your own events with those names.
-   In our 30 October 2026 test, a form sent with `fetch` after `event.preventDefault()` got `form_start` but never `form_submit`. Send `generate_lead` after a confirmed lead instead.
-   Build a closed funnel exploration from `form_start` to `generate_lead`, filtered on `form_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](https://support.google.com/analytics/answer/9216061), 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](https://support.google.com/analytics/answer/13316687), 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_start` waits for the visitor to leave a changed field.** Typing alone sent nothing; leaving the field did. The tag listens for the browser's `change` event, 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 called `event.preventDefault()` and sent the data with `fetch`. The tag saw the submit, marked it as cancelled (`gtm.formCanceled: true` in the data layer) and sent nothing.
-   **A form sent from a button's click handler got no `form_submit` either.** 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](https://developers.google.com/analytics/devguides/collection/ga4/reference/events?client_type=gtag#generate_lead) ("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](https://support.google.com/analytics/answer/13128484)). 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](https://support.google.com/analytics/answer/6366371)). Event names can be up to 40 characters and parameter values up to 100 ([collection limits](https://support.google.com/analytics/answer/9267744)).

### 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](https://support.google.com/analytics/answer/14240153)).

### 5\. Check it in DebugView

Turn on debug mode for your own browser with [Tag Assistant](https://tagassistant.google.com) (or Tag Manager's Preview), then open Admin, Data display, DebugView ([Google's DebugView page](https://support.google.com/analytics/answer/7201382)). 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

1.  In **Explore**, start a **Funnel exploration**.
2.  **Step 1, Started:** event `form_start`, with the parameter `form_id` exactly matching `contact`.
3.  **Step 2, Sent:** event `generate_lead` with the same `form_id` condition. Leave it as "is indirectly followed by"; add "Within" 30 minutes if you only want people who finished in one sitting.
4.  Leave **Make open funnel** off. A closed funnel only counts people who entered at step 1.
5.  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](https://support.google.com/analytics/answer/9327974)). 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](/guides/form-abandonment).

## 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.

1.  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 });
    ```
    
2.  Create a **Data Layer Variable** named `form_id` (and one named `field_name` for the field events).
3.  Create a **Custom Event** trigger with the event name `lead_sent`.
4.  Create a **Google Analytics: GA4 Event** tag with your measurement ID, the event name `generate_lead` and an event parameter `form_id` set to the variable. Fire it on the `lead_sent` trigger.
5.  For field events, add this as a **Custom HTML** tag fired on All Pages, then a Custom Event trigger for `form_field_complete` and a GA4 Event tag that sends `form_field_complete` with `form_id` and `field_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>
    ```
    
6.  Click **Preview** to test with Tag Assistant, then **Submit** and publish ([Google's GA4 events in Tag Manager](https://support.google.com/tagmanager/answer/13034206)).

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](https://support.google.com/tagmanager/answer/7679217)). 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](/guides/form-abandonment) covers the law). OllieLeads does that for forms you build yourself ([partial submissions](/features/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 as `page_location`. Use POST. [Data redaction](https://support.google.com/analytics/answer/13544947) in the web stream's settings removes email addresses on a best-effort basis, as a backstop.
-   **Whether the lead reached you.** `generate_lead` says your backend answered OK. If the email notification never arrives, that is a delivery problem: see [contact form not sending email](/blog/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](https://superduperseo.com/published-by)
