Connecting Contact Form 7 to GA4
Contact Form 7 is free, everywhere, and stores nothing by default — which is the whole problem. GA4 is where the business goes to understand what happened, which makes an unattributed conversion there expensive in decisions rather than in ad spend. The join between them succeeds or fails on one thing: whether the GA4 client_id reaches the Contact Form 7 submission before the conversion happens, because GA4 can only match a conversion it can tie back to a click it recognizes.
Free30 minutesNo obligation
Specific to this pair
Contact Form 7 leads in GA4 without the (direct)/(none) problem
Send Contact Form 7 leads to GA4 as generate_lead through Measurement Protocol from the wpcf7_mail_sent hook, reading client_id and session_id from the _ga cookies on the same-domain AJAX request. Remove any GTM wpcf7mailsent listener that also sends generate_lead, since GA4 won't deduplicate, and send only form ID and page path, never typed fields.
A Contact Form 7 lead should reach GA4 as generate_lead. Because the form posts over AJAX to your own WordPress REST endpoint, the browser sends your first-party cookies with the request, so the PHP side can read _ga for the client_id and the _ga_<measurement-id> cookie for session_id directly. On a same-domain form no hidden field is needed for GA4. Fire the Measurement Protocol call from the wpcf7_mail_sent hook so it only goes out for submissions that passed validation and spam checks.
The most common GA4 failure with this plugin is a double count, not a missing lead. Many sites already have a GTM listener on the wpcf7mailsent DOM event pushing generate_lead from the browser. Add a server event on top and GA4 records two leads, because Measurement Protocol has no deduplication. Decide which side owns generate_lead and remove the other.
Do not put the form's field values into event parameters. GA4's terms prohibit personally identifiable information, and a careless mapping of CF7's posted data will push names and email addresses straight into GA4. Send the form ID and page path, nothing the visitor typed. Mark generate_lead as a key event if you want it in conversion reports, and remember the stored entries, not GA4, are the record you reconcile against.
Contact Form 7 → GA4 · what breaks
Why this join usually fails — Contact Form 7 → GA4
- 01
The GA4 client_id never reaches Contact Form 7
There is no entries table to write to, so the identifier has to be captured into a hidden field and persisted by whatever you add for storage. Contact Form 7 submits over AJAX, so the page does not reload and anything expecting a navigation event never fires.
- 02
AJAX submission means no page load to hook
Setups that fire a conversion on thank-you-page load simply never fire. Setups that listen for the DOM event miss anyone whose script execution was blocked or deferred.
- 03
Everything lands under (direct)/(none)
The classic Measurement Protocol symptom, and it always means the same thing: the client_id was missing or wrong, so GA4 had no session to attach the event to and invented a new user.
- 04
Nothing reconciles afterwards
GA4 DebugView shows events arriving with their parameters, and Realtime confirms the user and session they attached to. Neither tells you whether you are double-counting — for that, compare GA4's conversion count against the source system's record for the same window.
Contact Form 7 → GA4 · the fix
What working looks like — Contact Form 7 → GA4
The GA4 client_id on every submission
Conversions arriving in GA4
Conversions appear in GA4 attributed to the original session's source and medium, so acquisition reporting reflects what actually drove revenue.
Reconciled against the source
Contact Form 7 → GA4 · architecture
How Contact Form 7 and GA4 actually connect
- 1
Contact Form 7 holds the truth
Out of the box Contact Form 7 emails the submission and keeps no record. Before any attribution work is meaningful, submissions have to be stored somewhere queryable. That is step one and it is not optional.
- 2
GA4 needs a click to match
GA4 needs the client_id to attach a server event to the user and session that produced the click. Without it there is no attribution to inherit and the event is orphaned.
- 3
The identifier is the bridge
The GA4 client_id, captured at the visit and carried into the Contact Form 7 submission.
- 4
The event source decides reliability
Fire server-side from PHP. The browser-side wpcf7mailsent DOM event exists and is commonly used, but it depends on the visitor's browser executing correctly and on the AJAX response arriving — the server hook does not.
- 5
Deduplication at the destination
GA4 has no built-in deduplication for Measurement Protocol. If the browser also sends the event, it counts twice. The only reliable approach is to decide per event which side sends it, and enforce that decision — GA4 will not protect you from your own duplicates.
Contact Form 7 → GA4 · normal or broken
Working vs. broken, side by side — Contact Form 7 → GA4
Normal, not a fault
The GA4 client_id present on the submission
Sent from the wpcf7_mail_sent hook
Deduplicated correctly
GA4 has no built-in deduplication for Measurement Protocol. If the browser also sends the event, it counts twice. The only reliable approach is to decide per event which side sends it, and enforce that decision — GA4 will not protect you from your own duplicates.
Reconciles with the source
Actually broken
The GA4 client_id missing on most records
There is no entries table to write to, so the identifier has to be captured into a hidden field and persisted by whatever you add for storage. Contact Form 7 submits over AJAX, so the page does not reload and anything expecting a navigation event never fires.
Sent from the browser
Setups that fire a conversion on thank-you-page load simply never fire. Setups that listen for the DOM event miss anyone whose script execution was blocked or deferred.
Counted twice, or not at all
The classic Measurement Protocol symptom, and it always means the same thing: the client_id was missing or wrong, so GA4 had no session to attach the event to and invented a new user.
Nobody has reconciled it
Contact Form 7 → GA4 · diagnose
Diagnostic order — Contact Form 7 → GA4
- 01
Count submissions carrying the GA4 client_id
- 02
Follow one real conversion end to end
- 03
Confirm submissions are being stored at all
If the only record is an email inbox, there is nothing to reconcile and no way to prove any number. Fix storage before touching attribution.
- 04
Check whether server events carry a session and a source
In GA4 Realtime or DebugView, find the event and look at its session and traffic source. A brand-new user with no source on every server event is the missing-client_id signature.
- 05
Read the match rate, not the success message
GA4 DebugView shows events arriving with their parameters, and Realtime confirms the user and session they attached to. Neither tells you whether you are double-counting — for that, compare GA4's conversion count against the source system's record for the same window.
Contact Form 7 → GA4 · prove
How it gets proven — Contact Form 7 → GA4
Source of truth
Whatever you configure to persist submissions — an entries plugin, or your own database table. By default there is nothing to reconcile against.
- The GA4 client_id confirmed present on a live submission.
- Server events confirmed in DebugView attaching to an existing session with the original traffic source intact.
- Stored submissions confirmed to match the email record for the same period, before any attribution claim is made.
Contact Form 7 → GA4 · free audit
What we check in the free audit
- Count submissions carrying the GA4 client_id
- Follow one real conversion end to end
- Confirm submissions are being stored at all
- Check whether server events carry a session and a source
- Read the match rate, not the success message
Checked against
Whatever you configure to persist submissions — an entries plugin, or your own database table. By default there is nothing to reconcile against.
Free30 minutesNo obligation
Contact Form 7 → GA4 · when not to buy
When you don't need this — Contact Form 7 → GA4
- Measure identifier coverage yourself first — pull your last hundred submissions and count how many carry the GA4 client_id.
- If you are on Contact Form 7 and serious about attribution, moving to a form plugin that stores entries is often cheaper than working around the one that does not. We will say so rather than bill for the workaround.
- GA4 is reporting, not bidding. If your problem is ad platform optimization, fix the ad platform destination first — GA4 will not change what any campaign does.
Contact Form 7 → GA4 · what we deliver
What gets built — Contact Form 7 → GA4
Identifier capture into Contact Form 7
Hidden field populated from a first-party cookie, plus a storage layer so the submission and its identifier survive past the email. The wpcf7_before_send_mail hook is where both happen.
Event source: the wpcf7_mail_sent hook
Fire server-side from PHP. The browser-side wpcf7mailsent DOM event exists and is commonly used, but it depends on the visitor's browser executing correctly and on the AJAX response arriving — the server hook does not.
Dispatch to GA4
Measurement Protocol events with the client_id, session_id and the correct measurement ID and API secret, sent server-side.
Reversals where they exist
Nothing to reverse. Spam filtering before the send is the equivalent concern.
Written guarantee
95%+ tracking accuracy
Measured against Whatever you configure to persist submissions — an entries plugin, or your own database table. By default there is nothing to reconcile against.. Miss it and we keep working at no cost.
From $400 · See packages
FAQ
Contact Form 7 → GA4 questions, answered
In the browser, listen for the wpcf7mailsent event in GTM; server-side, send generate_lead via Measurement Protocol from the wpcf7_mail_sent hook with the visitor's _ga client_id. Use one or the other for the same lead, not both.
Contact Form 7 submits over AJAX and does not load a new page unless you add a redirect, so a page-view trigger never runs. Trigger on the submission event instead.
Almost always because the GA4 client_id never reached the Contact Form 7 submission. There is no entries table to write to, so the identifier has to be captured into a hidden field and persisted by whatever you add for storage. Contact Form 7 submits over AJAX, so the page does not reload and anything expecting a navigation event never fires. Measure coverage before debugging the dispatch.
The client_id, from the _ga cookie. Measurement Protocol events without it are rejected or create a brand-new user with no session and no source, which is how server events end up filed under (direct)/(none) and make paid channels look worse than they are. Send session_id alongside it so the event joins the existing session, and include engagement_time_msec or the event may not appear as expected in reports.
The wpcf7_mail_sent hook. Fire server-side from PHP. The browser-side wpcf7mailsent DOM event exists and is commonly used, but it depends on the visitor's browser executing correctly and on the AJAX response arriving — the server hook does not.
GA4 DebugView shows events arriving with their parameters, and Realtime confirms the user and session they attached to. Neither tells you whether you are double-counting — for that, compare GA4's conversion count against the source system's record for the same window.
More on Contact Form 7 → GA4
- Contact Form 7 + GoogleGoogle Ads and GA4 tracking for Contact Form 7
- Contact Form 7 → Google AdsConnecting Contact Form 7 to Google Ads
- Contact Form 7 → MetaConnecting Contact Form 7 to Meta Conversions API
RELATED GUIDES
Where Contact Form 7 → GA4 fits in the rest of your tracking
- Fluent Forms → GA4Connecting Fluent Forms to GA4
- Tally → GA4Connecting Tally to GA4
- Formidable Forms → GA4Connecting Formidable Forms to GA4
- Contact Form 7 → MixpanelConnecting Contact Form 7 to Mixpanel
- Contact Form 7 → PostHogConnecting Contact Form 7 to PostHog
- Home servicesConversion tracking for home service businesses, measured in completed jobs
- Cognito Forms → GA4Connecting Cognito Forms to GA4
- WPForms → GA4Connecting WPForms to GA4
Free · 30 min
Get Contact Form 7 and GA4 joined properly
Free30 minutesNo obligation
