Form tracking is the most common tracking request and the most commonly wrong. The usual setups fire on the button, fire on a thank-you page anyone can open, or fire twice. This guide ranks the four methods, gives code for each, and shows the single event that feeds GA4, Google Ads and Meta.
The Goal
One dataLayer event, form_submit_success, pushed once per successful submission, carrying the form name, form ID and, where available, the submitter's email and phone. Everything downstream hangs off it.
Method 1: dataLayer Push on Confirmed Success
The best option. The form tool's success callback, or your own code's successful server response, pushes the event:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'form_submit_success',
form_tool: 'custom',
form_name: 'Contact',
form_id: 'contact-main',
user_email: 'name@example.com',
user_phone: '+15551234567'
});
Every major form tool exposes a success hook, and this site has guides with the exact listener for Elementor, Gravity Forms, WPForms, Fluent Forms, HubSpot, JotForm, Typeform and Tally. For a custom form, add the push in the code that handles the server's success response.
Method 2: Thank-You Page
If the form redirects to a dedicated page after success:
- Create a Page View trigger where Page Path equals
/thank-you. - Fire the GA4, Google Ads and Meta tags on it.
- Add a
noindexmeta tag to the page, remove it from the sitemap and navigation. - If several forms share the page, pass a parameter in the redirect such as
?form=contactand read it with a URL variable.
Weakness: anyone who bookmarks or refreshes the page counts again. Mitigate with Count set to One in Google Ads and with a cookie check in GTM that skips the tag if the same visitor converted in the last few minutes.
Method 3: Success Message Visibility
For forms that show a message in place and give you no hook, use the Element Visibility trigger:
- Find the CSS selector of the success message container, for example
.form-successor.wpcf7-mail-sent-ok. - Create an Element Visibility trigger: selection method CSS selector, that selector, fire once per page, minimum visible 50 percent, and enable Observe DOM changes.
- Push a dataLayer event from a small Custom HTML tag on that trigger so the rest of the setup stays the same:
<script>
window.dataLayer.push({
event: 'form_submit_success',
form_tool: 'visibility',
form_name: {{Form Name Lookup}},
form_id: 'unknown'
});
</script>
Weakness: the selector changes if the theme or plugin updates, and an error message that reuses the same container class will count. Check the selector is unique to success.
Method 4: Built-In Form Submission Trigger, Validated
GTM's Form Submission trigger listens for the browser's submit event. It fires before validation and before the server responds, so on its own it overcounts. It is acceptable only when:
- The form uses standard HTML submission with a page reload, and
- You enable Check Validation on the trigger, and
- You add conditions on Form ID or Form Classes so only the right form fires it.
Even then, a server-side validation error after submit will have already counted. Treat it as a last resort.
The Downstream Tags
Create Data Layer Variables for form_name, form_id, user_email and user_phone, and a Custom Event trigger on form_submit_success.
- GA4 Event
generate_leadwithform_name,form_id,valueandcurrency. Mark as a key event. - Google Ads Conversion Tracking with the lead conversion ID and label, a User-Provided Data variable from email and phone for Enhanced Conversions, and the Conversion Linker on All Pages.
- Meta Pixel Lead with
content_nameset to the form name, advanced matching from email and phone, and an event ID for Conversions API deduplication.
To fire a different Google Ads conversion for a specific form, duplicate the trigger with a condition form_id equals that form.
Spam and Quality
- Honeypot field or reCAPTCHA v3 on the form.
- A dropdown such as "I am a..." with values that let you flag competitors, students and vendors; push the value and exclude those from the primary conversion. The contact form on this site does exactly that.
- Count set to One in Google Ads.
Test
- GTM Preview. Submit the form with test data. Confirm
form_submit_successappears once with the right form name and that GA4, Google Ads and Meta each fired once. - Submit with a required field empty. Nothing should fire.
- Submit twice quickly. One conversion.
- For Method 2, open the thank-you URL directly and note that it fires; that is the known weakness.
- Check GA4 DebugView, Google Ads Diagnostics the next day and Meta Test Events.
Outcome
One event per real submission, feeding three platforms with the same definition of a lead, with the submitter's details attached for matching. From here, the CRM stage guides on this site show how to tell the platforms which of those leads turned into customers.
