Loading tutorials…
Loading tutorials…
The default Form Submission trigger handles 70% of forms. The other 30% (AJAX forms, React forms, Webflow forms) need a custom pattern. This walks through both, and which one you need.
Who this is forSite owners who want form submissions tracked as GA4 events, Meta leads, or Google Ads conversions. Most marketing forms (Contact Form 7, Gravity Forms, native HTML <form>) work with the default trigger. AJAX-submitted forms (HubSpot, Mailchimp embed, React) need the custom approach.
What you'll need
Step 1
GTM Built-in Form variables are off by default. Enable them so triggers and tags can reference Form ID, Form Classes, Form Element, etc.
GTM → Variables → Configure (in the Built-in Variables section).
Toggle ON: Form Element, Form ID, Form Classes, Form URL, Form Text.
These variables auto-populate when a form submission event fires, so your trigger conditions and tag parameters can use them.
Save.
Step 2
In the browser, inspect the form element. Note the id attribute. This is what GTM will match against.
Open the live page with the form. Right-click on the form → Inspect.
Look at the <form> tag in the Elements panel. Note the id attribute: e.g., id='contact-form-7' or id='hsForm_abc123'.
If the form has no id, look for a stable class (class="wpcf7-form" for Contact Form 7) — you'll use Form Classes in the trigger condition instead.
In DevTools → Network tab, submit a test entry. Watch for the form submission request. If it's a traditional POST that reloads the page, the default trigger works. If it's an XHR/fetch (no reload), you have an AJAX form — see step 5.
Step 3
Triggers → New → Form Submission. Configure to fire only on the specific form.
Triggers → New → Trigger Configuration → Choose Trigger Type → Form Submission.
Enable 'Wait for Tags' (timeout 2000ms) — this ensures any tracking tags fire before the form actually submits to the server.
Enable 'Check Validation' — this ensures only successful submits trigger; validation errors don't.
Choose "Some Forms" (not All Forms — All Forms catches phantom submits and validation events).
Condition: Form ID equals "contact-form-7" (or whatever you noted in step 2). Use Form Classes contains "wpcf7-form" as a fallback if Form ID isn't reliable.
Trigger name: "Form Submit — Contact Page Form." Save.
Step 4
Tags → New → GA4 Event. Event name: generate_lead (or similar). Trigger: the trigger from step 3.
Tags → New → Tag Configuration → Google Analytics: GA4 Event.
Measurement ID: G-XXXXX.
Event Name: 'generate_lead' (Meta's recommended for B2B leads), or 'contact_form_submit' if you want it more granular.
Event Parameters: form_id → {{Form ID}}, page_path → {{Page Path}}.
Trigger: the Form Submission trigger from step 3.
Tag name: "GA4 — Contact Form Submit." Save.
Step 5
If the form doesn't reload the page on submit, the default Form Submission trigger fails. Use a dataLayer push on AJAX success + Custom Event trigger.
Most AJAX form plugins fire a JavaScript event or callback on success. Examples: Contact Form 7 fires "wpcf7mailsent" on document. Gravity Forms uses jQuery: gform_confirmation_loaded. HubSpot fires the onFormSubmitted callback.
Pattern: add a small <script> in the page (or via Custom HTML tag set to fire on Page View) that listens for the form's success event and pushes to dataLayer.
Example for Contact Form 7: document.addEventListener('wpcf7mailsent', function(event) { window.dataLayer.push({event: 'cf7_submit', form_id: event.detail.contactFormId}); });
In GTM, create a Custom Event trigger: Event Name = "cf7_submit." Tag fires on this trigger.
Alternative pattern: redirect the form to a thank-you page on success. Then your trigger is Page View where Page Path equals '/thank-you'. Simpler but only works if a redirect is acceptable UX.
Step 6
Click Preview in GTM. Submit a test form. Verify the tag fires ONCE with the correct parameters.
GTM → Preview → Tag Assistant opens.
Navigate to the form page. Fill out the form with test data.
Submit. Watch the Tags column.
Your form submit tag should fire ONCE. Check the Variables tab to verify Form ID, Page Path are correctly populated.
Submit again without filling required fields (trigger validation error). The tag should NOT fire — Check Validation is doing its job.
Submit a third time with valid data on a DIFFERENT page that also has the form (if applicable). Verify it still fires.
Step 7
Publish the workspace. Watch GA4 events for the first 4 weeks for unexpected volume or zero fires.
GTM → Submit → Version name: 'v4 — Contact form submit tracking.' Publish.
Day 1: submit a real form yourself. Verify the event appears in GA4 → Realtime within 60 seconds.
Day 7: check event volume. If it's 5x what you expected, you may be catching validation errors or phantom submits — review the Wait for Tags + Check Validation settings.
Day 14: check Meta Events Manager (if you fire a Lead event there too) for Event Match Quality of the leads. Should be 6+ if Advanced Matching is on.
Day 30: any forms added since launch? They need their own trigger configured — there's no global 'all forms' tracking unless you specifically build it.
Common mistakes
Using 'All Forms' instead of 'Some Forms'
What goes wrong: Every form on the site (including search bars, login forms, and embedded third-party forms) fires the trigger. Lead count inflates 5-10x. Your conversion-optimized ad campaigns over-bid on phantom leads.
How to avoid: Always use "Some Forms" with a Form ID or Form Classes condition. Even on a single-form page — future you will add another form and forget to update the trigger.
Not enabling 'Check Validation'
What goes wrong: Every form interaction — including failed validations — fires the conversion event. Your lead count inflates and Smart Bidding chases phantom leads.
How to avoid: In the Form Submission trigger, ENABLE Check Validation. This filters out unsuccessful submits.
Trying to use the default trigger on AJAX forms
What goes wrong: The default Form Submission trigger only catches submits that cause a page reload. AJAX forms (HubSpot, Webflow, React, Gravity Forms with AJAX enabled) submit without reloading — the trigger never fires. You think tracking works because no errors appear; it just silently misses every conversion.
How to avoid: Use a Custom Event trigger on the form library's success callback, or redirect to a thank-you page and trigger on Page View.
Firing the same form submit to GA4 + Meta + Google Ads as separate tags
What goes wrong: Three separate tags fire on each submit. If one fails, you have inconsistent data across platforms. Worse, you forget to update one when the form changes.
How to avoid: Use the same trigger for all three tags. Or use a single GA4 event and rely on GA4 → Google Ads import + Meta CAPI to forward downstream. Single source of truth.
Not tracking which form when there are multiple
What goes wrong: All form submits show as 'generate_lead' with no distinguishing parameter. Reports show 47 leads but you can't tell if they came from the contact form, demo request, or newsletter.
How to avoid: Always pass form_id, form_name, or page_path as parameters. GA4 → Reports → custom report can then segment leads by source form.
Recap
Done — what's next
How to fire GA4 through Google Tag Manager — without double-counting
Read the next tutorial
Hand it off
Form tracking is the boring foundation of every B2B and lead-gen ad campaign. Getting it wrong means broken ROI math for months. A GTM specialist will configure each form, set up the right trigger (default vs AJAX vs thank-you), and validate end-to-end. Typically $150-300 one-time at $14-16/hr.
See specialist rates
You forgot to enable 'Check Validation' in the trigger settings. Without it, the trigger fires on every submit attempt — successful or not. Edit the trigger, enable Check Validation, save and republish.
HubSpot embed forms are AJAX. Use the onFormSubmitted callback in the HubSpot embed snippet: hbspt.forms.create({..., onFormSubmitted: function() { window.dataLayer.push({event: 'hubspot_form_submit'}); }}). In GTM, create a Custom Event trigger for 'hubspot_form_submit'.
Listen for the gform_confirmation_loaded jQuery event: jQuery(document).on('gform_confirmation_loaded', function(event, formId) { window.dataLayer.push({event: 'gravity_submit', form_id: formId}); }); Custom Event trigger on 'gravity_submit'.
Yes, but track meaningful steps. Push a dataLayer event on step completion (form_step_complete with step_number) and the final form submit. Avoid tracking every field change — too noisy.
Three possibilities: (1) the event isn't marked as a Key Event in GA4 — toggle in Admin → Events; (2) GA4 has a 24-48 hour processing delay for non-Realtime reports; (3) you're filtering reports by date range that excludes the test fires.
Google Tag Manager
Firing GA4 through GTM is the foundation of every modern analytics stack. Done right, it's one tag and a clean event map. Done wrong, you double-count every event and pollute six months of reports. This is the right way.
Google Tag Manager
Meta doesn't ship a native GTM tag template, so the install is a Custom HTML tag. Done right, this is cleaner than the official Meta pixel-injection method — done wrong, you double-fire every event and the Events Manager fills with junk.
Google Tag Manager
Every GTM tutorial assumes you know what dataLayer is. Most don't actually explain it. This one does — in plain English, with examples you can copy. No JavaScript prerequisites.
Google Tag Manager
If a tag isn't firing — or worse, firing when it shouldn't — Tag Assistant is the only honest source of truth. Most operators use it wrong. This walks through the workflow specialists actually use.
Google Tag Manager
DIY GTM works fine for simple stacks. It starts breaking down when you need GA4 + Meta + TikTok + Google Ads all firing accurately with deduplication. Here's the honest framework for when to hire.