On this page · 10 sections
- Why the usual Elementor tracking methods miscount
- What you need before you start
- Step 1: Give every form a stable name and ID
- Step 2: Push generate_lead to the dataLayer only on success
- Step 3: Build the GTM trigger and the GA4 event tag
- Step 4: Mark generate_lead as a key event in GA4
- Step 5: Count the lead in Google Ads
- Step 6: Store UTM parameters and the GCLID on the lead
- Test it before you trust it
- Common problems and how to fix them
The reliable way to track Elementor form submissions with Google Tag Manager is to listen for the event Elementor Pro fires only after a form is accepted, submit_success, and push a generate_lead event to the dataLayer at that moment. In GTM, a Custom Event trigger on generate_lead then fires your GA4 event tag and your Google Ads conversion. Button-click and generic form-submit triggers count failed and spam attempts, so they don’t give you lead numbers you can trust.
The short version:
- Give each Elementor form a stable Form name and Form ID.
- Add a small listener that pushes
generate_leadto the dataLayer onsubmit_success. - In GTM, fire a GA4 event tag from a Custom Event trigger, then mark
generate_leadas a key event in GA4. - Count the same lead in Google Ads, either by importing the GA4 key event or with a Google Ads conversion tag.
- Save UTM parameters and the GCLID in hidden fields, so each lead record shows where it came from.
This is one check from the tracking part of a MarTech audit, and on Elementor sites it’s the one I find broken most often. Below is the setup I use, step by step, with the reasons behind each choice.
Why the usual Elementor tracking methods miscount
Elementor Pro’s Form widget doesn’t submit the page the way a plain HTML form does. When a visitor clicks the button, Elementor cancels the browser’s normal submit, sends the data in the background (AJAX) and waits for the server’s reply. Only if the server accepts the submission does it show the success message and trigger a jQuery event called submit_success on the form. If validation fails, it triggers error instead. I checked this against the Elementor Pro 4.3 front-end code in October 2026.
That’s why most quick setups count the wrong thing:
| Method | What it actually counts | Verdict |
|---|---|---|
| GTM click trigger on the submit button | Every click, including empty fields, validation errors and double clicks | Inflated lead numbers |
| GTM Form Submission trigger | The browser’s submit event, which Elementor overrides. With “Check Validation” on, it often doesn’t fire at all. With it off, it fires on attempts, not successes | Unreliable |
GA4 enhanced measurement form_submit | Form submit attempts. It has no idea whether Elementor accepted the data | Fine for engagement, not for leads |
| Thank-you page view | Page views of the thank-you URL, including refreshes and direct visits. Only works if you use the Redirect action | Workable, but noisy |
submit_success listener → dataLayer | Submissions Elementor accepted | Use this |
Google’s own documentation for the GTM Custom Event trigger describes this exact case: tracking a form whose default submit behaviour has been overridden.
What you need before you start
- Elementor Pro with the classic Form widget. This guide was checked against Elementor Pro 4.3 (October 2026).
- A Google Tag Manager container installed on every page, with your Google tag (GA4 measurement ID) firing on all pages.
- A GA4 property and Editor (or higher) access to it.
- For Google Ads: your Ads account linked to GA4, with auto-tagging turned on. Auto-tagging adds the GCLID to your ad clicks.
A note on Elementor’s newer editor: Editor 4 includes a separate Atomic Form element, built from individual input “atoms”. Elementor doesn’t document which front-end events it fires, so don’t assume the listener below works with it. If you build forms with the Atomic Form, test in GTM Preview before relying on it, or keep lead forms on the classic Form widget for now.
Step 1: Give every form a stable name and ID
In the Elementor editor, open the Form widget. Set a clear Form name (for example, “Free MarTech audit”) and, under Additional Options, a unique Form ID (for example, free_audit). Elementor outputs these on the page as the form’s name and id attributes:
<form class="elementor-form" id="free_audit" name="Free MarTech audit">
Without them you can’t tell your contact form from your demo form in GA4. Use the same naming pattern on every site you manage. Clean, stable names are what make the reports readable a year later.
Step 2: Push generate_lead to the dataLayer only on success
In GTM, create a Custom HTML tag with the code below and fire it on a DOM Ready trigger for all pages. DOM Ready makes sure jQuery, which Elementor Pro loads, is available. You can also add the same script to the site footer with a code-snippets plugin. Use one place only, or every lead will be counted twice.
<script>
(function () {
if (!window.jQuery) return;
window.dataLayer = window.dataLayer || [];
// Elementor Pro triggers "submit_success" (a jQuery event) on the form
// only after the server accepts the submission.
jQuery(document).on('submit_success', '.elementor-form', function (event) {
var form = event.currentTarget;
window.dataLayer.push({
event: 'generate_lead',
form_id: form.getAttribute('id') || '',
form_name: form.getAttribute('name') || ''
});
});
})();
</script>
Three details matter here:
- It’s a jQuery event.
submit_successis triggered with jQuery, so a nativeaddEventListenerwon’t hear it. Listen with jQuery, as above. - The listener is delegated to the document, so it also catches forms inside Elementor popups that load after the page.
- No personal data goes into the dataLayer. Don’t push names, emails or phone numbers into GA4. Google’s policies don’t allow personally identifiable information in Analytics.
Step 3: Build the GTM trigger and the GA4 event tag
- Variables. Create two Data Layer Variables:
form_idandform_name. - Trigger. Create a Custom Event trigger with the event name
generate_lead, firing on all custom events. - Tag. Create a Google Analytics: GA4 Event tag. Enter your Measurement ID, set the event name to
generate_lead, and add the event parametersform_idandform_nameusing the two variables. Attach the Custom Event trigger.
generate_lead is one of Google’s recommended events, defined as a lead generated, for example through a form. It also accepts value and currency. If you know roughly what a lead is worth, send both (the currency as a three-letter code such as SAR or USD). Google requires currency whenever a value is sent.
Step 4: Mark generate_lead as a key event in GA4
GA4 now calls conversions “key events”. As of October 2026, go to Admin → Data display → Events and click the star next to generate_lead to mark it as a key event. If the event hasn’t arrived yet, you can create it there ahead of time with the “Mark as key event” toggle on. Allow up to 24 hours for it to appear in standard reports. Realtime shows it within minutes.
To break leads down by form, register form_id and form_name as event-scoped custom dimensions. Until you do, GA4 collects the parameters but you can’t use them in reports.
Step 5: Count the lead in Google Ads
You have two good options. Pick one as the conversion Google Ads bids on.
Option A: Import the GA4 key event
With GA4 and Google Ads linked and auto-tagging on, you can create a Google Ads conversion from the GA4 key event. As of October 2026 you can do this in GA4 under Advertising → Conversion management → New conversion, or from the Google Ads side. It needs no extra tags, and GA4 and Ads report from the same event.
Option B: Google Ads conversion tag in GTM
In Google Ads, create a manual website conversion action and choose to set it up with Google Tag Manager. Copy the Conversion ID and Conversion Label. In GTM, add a Google Ads Conversion Tracking tag with those values and the same generate_lead trigger. Make sure click data is stored on landing pages: a Google tag on every page covers this, otherwise add a Conversion Linker tag. This route sends the conversion straight to Ads and is the usual starting point for enhanced conversions.
Whichever you choose, avoid counting one lead twice. If you set up both, make one the primary action (used for bidding) and the other secondary (reporting only). Not every lead arrives through a form, so once forms work, track WhatsApp leads the same way.
Step 6: Store UTM parameters and the GCLID on the lead
GA4 tells you how many leads a campaign produced. Your CRM needs to know which lead came from which campaign. The simplest way is to save the campaign parameters into hidden form fields, so they travel with the submission. To route this data on, you can also send the same submission to your CRM or n8n.
In the Form widget, add fields of type Hidden and set each field’s ID (on the field’s Advanced tab) to utm_source, utm_medium, utm_campaign, utm_term, utm_content and gclid. Elementor names them form_fields[utm_source] and so on. Then add this script to every page, in the same GTM container or footer as before:
<script>
(function () {
var keys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid'];
var params = new URLSearchParams(window.location.search);
var stored = {};
try { stored = JSON.parse(sessionStorage.getItem('lead_attribution') || '{}'); } catch (e) {}
// A new campaign visit replaces the stored values.
if (keys.some(function (k) { return params.get(k); })) {
stored = {};
keys.forEach(function (k) { if (params.get(k)) stored[k] = params.get(k); });
try { sessionStorage.setItem('lead_attribution', JSON.stringify(stored)); } catch (e) {}
}
function fillHiddenFields() {
keys.forEach(function (k) {
if (!stored[k]) return;
document.querySelectorAll('input[name="form_fields[' + k + ']"]').forEach(function (input) {
input.value = stored[k];
});
});
}
fillHiddenFields();
// Forms inside Elementor popups are added when the popup opens.
window.addEventListener('elementor/popup/show', fillHiddenFields);
})();
</script>
Why a script rather than Elementor’s Request Parameter dynamic tag? The dynamic tag fills the field on the server from the current URL. It works when the visitor submits on the landing page, but the values are lost once they click through to another page. It can also return stale values if your page cache serves the same HTML to every URL variant. The script keeps the values for the browser session and fills them in the browser. Session storage ends when the tab closes. If you need longer attribution windows, that’s a decision to make with your privacy and consent setup.
The GCLID only appears on ad clicks when auto-tagging is on. Storing it on the lead is what later lets you import offline conversions or use enhanced conversions for leads, so Google Ads learns which leads turned into customers, not just which ones filled in a form.
Test it before you trust it
- In GTM, click Preview, connect Tag Assistant to the page with your form, and open it with test parameters, for example
?utm_source=test&utm_medium=cpc&gclid=TEST123. - Submit the form with a required field empty. No
generate_leadshould appear. If it does, something other than this listener is firing. - Submit it properly. You should see exactly one
generate_leadevent with the rightform_id, and your GA4 (and Ads) tags under “Tags Fired”. - Open the submission in Elementor → Submissions (or your email/CRM) and check that the hidden fields hold your test values.
- In GA4, check Realtime (or DebugView) for
generate_lead.
If your form uses Elementor’s Redirect action, test that case specifically. Elementor redirects straight after the success event, which leaves your tags very little time to send. If the GA4 or Ads hit doesn’t arrive in testing, track the thank-you page as a backup or show an inline success message instead of redirecting. If you’d rather have someone else check all of this, my free GTM and GA4 health check covers form events, key events and Ads conversions.
Common problems and how to fix them
| Symptom | Likely cause | Fix |
|---|---|---|
Two generate_lead events per submit | The listener is installed twice (GTM and a plugin or footer snippet), or an old click trigger is still live | Keep one listener. Remove old click/form triggers from the lead tags |
| No event at all | The listener runs before jQuery loads, or the form is an Atomic Form or another plugin’s form | Fire the Custom HTML tag on DOM Ready. Confirm the form has the elementor-form class |
| Event fires in Preview, not in GA4 | Wrong Measurement ID, consent blocking the tag, or you’re looking at standard reports too early | Check the tag’s ID, check your consent setup, use Realtime/DebugView |
| GA4 has leads, Google Ads shows none | Accounts not linked, auto-tagging off, or the conversion was never created from the key event | Link the accounts, turn on auto-tagging, create the conversion (Step 5) |
| Ads leads ≈ double GA4 leads | Both the imported key event and the Ads tag are primary | Make one primary, the other secondary |
| Hidden fields empty | Field IDs don’t match the script’s keys, or the visitor arrived without parameters | Match the IDs exactly. Test with parameters in the URL |
Why this much care over one event? Because every optimisation decision rests on it. One of the results I’m proudest of is +30% lead submissions (after optimisation), and a number like that only means something if every accepted submission is counted once, and failed attempts aren’t counted at all. When I wire up tracking for clients, this event is the first thing I build and the last thing I re-test after any change to the site.
The submit event is only the start. Once leads reach your CRM, you can go further by reporting lead quality back to Meta.
More in Tracking & Analytics
Tracking & Analytics How to Track WhatsApp Clicks and WhatsApp Leads in GA4 and Google Ads
Track WhatsApp button clicks in GA4 and Google Ads, with or without GTM, and tie real WhatsApp conversations back to the campaigns that started them.
Automation & Integrations Why Is Zapier So Expensive? How to Cut Your Bill (and When to Move to n8n)
Zapier bills per task, and every action step counts. Find the Zaps eating your plan, cut them, and know when n8n or custom code is cheaper.