The average WordPress lead-gen site I audit runs two or three form plugins at once. Elementor on the landing pages, Contact Form 7 left over from the old theme, Gravity Forms on the quote calculator. Each submits differently, and one GTM trigger covering all of them is usually lying about at least two.
| GTM Form Submission | The plugin's success event |
|---|---|
| Listens for the native submit event | Elementor: submit_success |
| Fires on the press, before validation | Contact Form 7: wpcf7mailsent |
| Counts spam and bad emails as leads | Gravity: gform_confirmation_loaded |
| Misses AJAX submissions entirely | Only fires once the lead landed |
Why does GTM's Form Submission trigger misfire on WordPress forms?
Because GTM's built-in trigger listens for the browser's native submit event, and modern WordPress form plugins intercept that event to submit via AJAX. Two failure modes come out of that, pushing your numbers in opposite directions.
First, overcounting. The native submit event fires on the button press — before the plugin validates anything, before the server accepts anything. Empty required field? Spam-flagged? Trigger already fired. I've audited sites reporting double their real leads, with Smart Bidding optimizing toward people who type a bad email and leave.
Second, undercounting. Plenty of plugins call preventDefault early enough, or submit through their own JavaScript entirely, so the native event never propagates. The visitor submits, the lead arrives, your trigger hears nothing. Same trigger, opposite lie.
The fix: stop guessing from the button and listen for the plugin's own "this actually worked" signal. Every major form plugin exposes one. They're just all different.
How do I track Elementor Pro form submissions?
Elementor Pro fires a submit_success JavaScript event when the server confirms the submission — that's your hook. It's dispatched through jQuery (which Elementor loads anyway), so attach a listener and translate it into a dataLayer push:
jQuery(document).on('submit_success', function (e) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'generate_lead',
form_id: (e.target && e.target.id) ? e.target.id : 'elementor_form',
form_plugin: 'elementor'
});
});
Put this in a Custom HTML tag firing on All Pages (or in your child theme). Give each Elementor form widget a distinct ID in its Advanced settings — the demo request and the newsletter box should never share one conversion action.
Failed validation never fires submit_success. That's the whole point.
How do I track Contact Form 7 submissions?
Contact Form 7 dispatches a DOM event called wpcf7mailsent when the form validates and the mail actually sends. No jQuery needed:
document.addEventListener('wpcf7mailsent', function (event) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'generate_lead',
form_id: event.detail.contactFormId,
form_plugin: 'cf7'
});
}, false);
The event.detail.contactFormId gives you CF7's numeric form ID, so multiple forms on one site stay distinguishable without touching each form.
CF7 also fires wpcf7invalid on validation failure, wpcf7mailfailed when the mail send breaks, and wpcf7submit on every attempt. I keep seeing setups wired to wpcf7submit because it "always fires" — that's the overcounting problem with extra steps. Only wpcf7mailsent means a lead.
How do I track WPForms and Gravity Forms submissions?
With these two, the right hook depends on the configured confirmation type, so check that setting before building anything in GTM.
WPForms gives each form a confirmation type: show a message, show a page, or go to a URL. With "go to URL" pointed at a thank-you page, a plain pageview trigger works — as long as that page is unreachable any other way and has GTM installed. With an inline message (the default) and AJAX enabled, WPForms fires a wpformsAjaxSubmitSuccess jQuery event you can listen for like the Elementor snippet above. Message confirmation with AJAX off reloads the same URL — no URL change, no JS event, so you're stuck with a fragile element-visibility trigger on the confirmation text.
Gravity Forms has the same three confirmation flavors — text, page, or redirect — plus a per-embed AJAX option. With AJAX on, Gravity fires a gform_confirmation_loaded jQuery event and hands you the form ID:
jQuery(document).on('gform_confirmation_loaded', function (event, formId) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'generate_lead',
form_id: 'gf-' + formId,
form_plugin: 'gravity_forms'
});
});
With AJAX off and a text confirmation, Gravity does a full page reload and prints the confirmation inline — no distinct URL, no event, the worst of both worlds. Either turn AJAX on for the embed or switch the confirmation to a redirect, which then behaves like any thank-you page: pageview trigger, GTM on the destination, destination not reachable directly.
| Layer | What sits there |
|---|---|
| Inputs | Elementor submit_success, CF7 wpcf7mailsent, WPForms AJAX success, Gravity gform_confirmation_loaded |
| generate_lead push | One Custom Event trigger, one form_id variable, one event to debug |
| Destinations | Google Ads conversion, GA4 event, Meta Pixel Lead |
What's the one pattern that works across all these plugins?
Normalize everything into a single generate_lead dataLayer event and hang every conversion tag off it. Four plugins, four listeners, one event name. Then in GTM:
- One Custom Event trigger on
generate_lead. - A dataLayer variable for
form_id. - Your Google Ads conversion tag, GA4 event tag, and Meta Pixel Lead event all firing from that single trigger.
This buys you three things. Google Ads, GA4, and Meta finally describe the same moment, so their numbers stop disagreeing for structural reasons. A new form plugin later means one new listener, not three rebuilt tags. And debugging means watching one event in GTM Preview instead of untangling nine triggers. generate_lead is also GA4's recommended event name for leads.
Sending events server-side to Meta's Conversions API too? Add an event_id to the push and send the same ID both ways so Meta deduplicates.
- Optimizer enabled — Delay JavaScript until user interaction
- gtm.js held back — jQuery moved or combined as well
- Listener never binds — jQuery is not defined, the hook is lost
- Lead not reported — The form still works, so nobody notices
Can caching and optimizer plugins break this?
Constantly. WordPress performance plugins — WP Rocket, LiteSpeed Cache, Autoptimize and friends — defer, delay, combine, and minify JavaScript to chase PageSpeed scores, and tracking scripts are their favorite victims.
The classic failure: a "delay JavaScript execution" feature holds gtm.js back until the visitor interacts. Listeners load late, ordering gets scrambled, and jQuery-dependent snippets throw "jQuery is not defined" because the optimizer moved or combined jQuery. The nastiest version is intermittent — tracking works when you test it, and silently drops a slice of real visitors.
The fix is exclusions. In the optimizer's settings, exclude from delay/defer/combine: gtm.js, gtag, dataLayer, your listener snippets, and jQuery itself if your form plugins depend on it. Then retest in GTM Preview with the cache enabled — testing logged in as admin usually bypasses the cache and shows you a site your visitors never see.
What about cookie consent plugins?
Consent plugins like Complianz, CookieYes, and Borlabs sit in front of all of this, and their configuration decides whether you have a small gap or a crater. The blunt setups block GTM entirely until the visitor accepts — every non-consenting or not-yet-consenting visitor converts invisibly, listeners and all. Since Consent Mode v2 became mandatory for Google's EEA advertising features in 2024, the better pattern is loading GTM with consent defaults denied and letting Consent Mode govern what individual tags do. You keep conversion modeling instead of a blind spot. Most major WordPress consent plugins ship Consent Mode v2 integration now; turn it on, then verify the consent state in GTM Preview's Consent tab rather than trusting the settings page.
Do I need first-party server-side tracking for a WordPress lead-gen site?
If leads are how you make money, yes — because everything above still runs in the visitor's browser, and the browser is where tracking goes to die. Plain-language version, no jargon assumed.
Right now, when someone submits your form, a script in their browser sends the conversion to Google and Meta. Ad blockers kill those scripts outright — and lead-gen audiences, especially B2B and technical ones, run blockers at rates that should scare you. Safari's tracking prevention caps how long browser-set cookies survive, so a visitor who clicks your ad Monday and submits Thursday can look like two unrelated people. Every one of those losses happens after you've done the listener work perfectly.
Server-side tracking changes who does the sending. Your generate_lead event goes to a tagging server on your own subdomain — track.yourdomain.com, first-party, part of your site as far as browsers are concerned — and that server forwards the conversion to Google Ads, GA4, and Meta. Ad blockers don't block your own domain the way they block googletagmanager.com. Cookies set from your server outlive script-set ones, so Monday's click still connects to Thursday's lead. And since the form just collected an email address, the server can pass it along hashed — Google's Enhanced Conversions for Leads and Meta's Conversions API both use it to match conversions to ad clicks far more reliably, which feeds the bidding algorithms real data. One pipeline feeding every platform also means one source of truth: consistent numbers, proper dedup, full control over what's shared per consent.
The plugin-zoo work above is the foundation — clean success events are the input. The server layer makes them survive the trip.
Frequently asked questions
How do I track Elementor form submissions in Google Tag Manager?
Listen for Elementor Pro's submit_success jQuery event, push a generate_lead dataLayer event from it, and fire your tags off a Custom Event trigger. It only fires on confirmed success.
Does Contact Form 7 work with Google Ads conversion tracking?
Yes — listen for the wpcf7mailsent DOM event, push a dataLayer event when it fires, and trigger your Google Ads conversion tag from that. Don't use wpcf7submit; it fires on failed attempts too.
Why does GTM's Form Submission trigger fire when the form has errors?
Because it listens for the browser's native submit event, which fires on the button press before the plugin validates anything. Use the form plugin's own success event instead of the generic trigger.
Should I use a thank-you page or a dataLayer event for WordPress forms?
A dataLayer event from the plugin's success signal is more reliable, since most WordPress form plugins submit via AJAX with no page change. Thank-you pages only work for redirect confirmations that aren't reachable any other way.
Why did my form tracking stop working after installing WP Rocket?
Its JavaScript delay and defer features likely held back GTM or your listener snippets. Exclude gtm.js, dataLayer, jQuery, and your listeners from optimization, purge the cache, and retest with caching enabled.
Do I need server-side tracking for a WordPress lead-generation site?
If you buy ads for leads, yes. Browser-only tracking loses conversions to ad blockers and Safari's cookie limits, while a first-party server endpoint on your own subdomain keeps sending them and improves match quality with hashed form data.






