Skip to main content
Contact Form 7 → GA4Integration guide

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

  1. 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.

  2. 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.

  3. 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.

  4. 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. 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. 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. 3

    The identifier is the bridge

    The GA4 client_id, captured at the visit and carried into the Contact Form 7 submission.

  4. 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. 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

  1. 01

    Count submissions carrying the GA4 client_id

  2. 02

    Follow one real conversion end to end

  3. 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.

  4. 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.

  5. 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.

Get my free conversion tracking audit

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.

Get my free conversion tracking audit

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.

Free · 30 min

Get Contact Form 7 and GA4 joined properly

Free30 minutesNo obligation