Contact Form 7 UTM Tracking: How to Pass UTM Parameters With Every Lead
A Contact Form 7 submission can arrive with the name, email, and message perfectly intact while every campaign field is blank.
That is not a small reporting problem. It means the lead survived, but the reason the lead arrived did not.
When I need to pass UTM parameters through Contact Form 7, I treat it as a four-part handoff: capture the values, put them into real CF7 hidden fields, add the matching mail tags, and verify the final destination.
If any one of those steps is missing, the form can still show a success message. That is why this problem stays hidden.
The short version
| What you see | Most likely break | What I check first |
|---|---|---|
| Hidden input is empty before submit | Capture script, form-tag setup, caching, or script order | Inspect the rendered input value in the browser |
| Input has a value, but email is blank | Mail tag is missing or does not match the form field name | Compare the Form and Mail tabs character for character |
| Email shows a shortcode or form tag literally | WordPress shortcode syntax and CF7 form-tag syntax were mixed | Replace the legacy wrapper with a current CF7 hidden tag |
| Email has the value, but CRM is blank | Webhook, Zapier, or CRM field mapping | Inspect the outgoing payload and destination mapping |
| Tagged landing page works sometimes | Cache or loss between landing and conversion pages | Test a cached page and an untagged second page separately |
The important distinction is between a form tag and a mail tag.
The form tag creates the field that receives the value. The mail tag tells Contact Form 7 where to place the submitted value in the email template.
You usually need both.
Method 1: capture UTM parameters from the current URL
Contact Form 7 has a native hidden form tag. Its current source registers the hidden tag and renders it as a real HTML hidden input. The same source supports a default:get option that reads a query-string value with the same field name.
For a form that appears on the tagged landing page, the exact form-tag pattern is:
[hidden utm_source default:get]
[hidden utm_medium default:get]
[hidden utm_campaign default:get]
[hidden utm_term default:get]
[hidden utm_content default:get]
A visitor opening this URL:
https://www.example.com/contact/?utm_source=google&utm_medium=cpc&utm_campaign=summer_demo
should get a rendered input whose name is utm_source and whose value is google.
This method is simple. It is also narrow.
It reads the current request. If the visitor lands on one page, browses to an untagged contact page, and submits there, the contact page no longer has the original UTMs. A hidden field cannot remember a value it never received.
It is also sensitive to full-page caching because the hidden value is created when WordPress renders the form. If a cache ignores query strings and serves the same HTML to everyone, the form can receive an old or empty value.
How a CF7 hidden field becomes a mail value
The full path is worth spelling out because the square brackets can make the setup look more magical than it is.
- Contact Form 7 reads
[hidden utm_source default:get]in the Form tab. - On a request containing
utm_source=google, CF7 renders an HTML input namedutm_sourcewith the valuegoogle. - The browser includes that hidden input when the visitor submits the form.
- Contact Form 7 receives
utm_sourceas posted form data. - The
mail tag inserts that posted value into the Mail template.
The mail tag does not go back to the landing URL and look for UTMs. It reads the submitted field. This is why adding to the Mail tab without adding a form field does not create tracking.
The same boundary applies to every destination after Contact Form 7. Each step receives a named value from the step before it.
| Layer | Native example | HandL example |
|---|---|---|
| CF7 form field name | utm_source | utm_source_cf7 |
| Rendered HTML input name | utm_source | utm_source_cf7 |
| CF7 mail tag | | [utm_source_cf7] |
| Webhook or Zapier input | utm_source | utm_source_cf7 |
| CRM destination | Whatever CRM field you map | Whatever CRM field you map |
I do not rename fields casually after a form is live. A rename in the Form tab can break the Mail tab, stored-message expectations, Zapier samples, webhook payloads, and CRM mappings at the same time.
Method 2: capture once and populate the CF7 fields later
For a normal lead journey, I prefer to capture the attribution on arrival and keep it until the visitor converts.
That is the role of a persistence layer. It reads the landing values, stores them according to the site’s consent setup, and populates the form when Contact Form 7 is ready.
HandL UTM Grabber is one way to do this on WordPress. Its current Contact Form 7 integration guide uses these field tags:
[hidden utm_source_cf7 utm_source_cf7-{formid} class:utm_source id:utm_source]
[hidden utm_medium_cf7 utm_medium_cf7-{formid} class:utm_medium id:utm_medium]
[hidden utm_campaign_cf7 utm_campaign_cf7-{formid} class:utm_campaign id:utm_campaign]
[hidden utm_term_cf7 utm_term_cf7-{formid} class:utm_term id:utm_term]
[hidden utm_content_cf7 utm_content_cf7-{formid} class:utm_content id:utm_content]
The current integration also provides a UTM Fields (HandL) tag generator that inserts the fields for you. I would use that instead of copying the wrapper syntax from the 2018 version of this article.
The field name matters. In this setup, the submitted source field is utm_source_cf7, not utm_source. The Mail tab, webhook, Zapier step, and CRM mapping must use the name that your form actually submits.
This is where a “tracking” problem can really be a naming problem.
Add the matching mail tags
After adding hidden fields under the Form tab, open the Mail tab and include the matching mail tags in the message body.
For the native fields from Method 1:
Campaign source:
Campaign medium:
Campaign name:
Campaign term:
Campaign content:
For the HandL fields from Method 2:
Campaign source: [utm_source_cf7]
Campaign medium: [utm_medium_cf7]
Campaign name: [utm_campaign_cf7]
Campaign term: [utm_term_cf7]
Campaign content: [utm_content_cf7]
Contact Form 7 matches the mail tag to the submitted form field by name. If the Form tab says utm_campaign_cf7 and the Mail tab says utm_campaign, those are two different fields.
Where the value goes after submit
| Destination | What receives the value | What you must configure |
|---|---|---|
| Email notification | The CF7 mail tag in the Mail template | Add the mail tag that matches the hidden form field |
| Flamingo | The submitted field in the stored inbound message | Install and activate Flamingo; confirm the field appears in the saved message |
| CRM webhook or connector | The outgoing payload field | Map the submitted CF7 field name to the correct CRM property |
| Zapier | The field exposed by the Contact Form 7 trigger or connector | Refresh the trigger sample, then map that field in the next Zap step |
Contact Form 7 does not store submitted messages by itself. The official Flamingo plugin exists for that job and stores the submission data in the WordPress database.
A webhook or CRM handoff is separate again. Contact Form 7’s default plugin description says it does not send data to external servers by itself. An integration has to do that work.
If you are using Zapier, keep the handoff as a separate step. My Zapier for Contact Form 7 walkthrough covers that downstream path. I would not merge it into this setup because the two pages answer different questions: this one gets the value into the submission; the Zapier article moves it onward.
Why Contact Form 7 UTM tracking breaks
1. The hidden field renders empty
Inspect the field before submitting. For the native setup, run this in the browser console:
document.querySelector('input[name="utm_source"]')?.value
For the HandL setup, use utm_source_cf7 instead.
If the result is empty, the failure happened before the mail or CRM step. Check whether the URL contains the value, whether the form tag is correct, and whether the capture script runs after the form field exists.
This script-order problem appears with forms loaded in a popup, modal, or delayed page section. Custom JavaScript can run successfully and still find nothing because Contact Form 7 has not rendered the input yet.
2. The value arrives as a literal shortcode string
Contact Form 7 form tags and WordPress shortcodes are not interchangeable.
If an email contains something like [utm_source_i] or the page prints the wrapper instead of a hidden input, the syntax was inserted into a context that no longer evaluates it. Replace the legacy wrapper with a current CF7 hidden tag or the current HandL tag generator output.
3. The email has the value, but the CRM does not
This is good news, in a limited way. The browser capture and Contact Form 7 submission worked.
Now inspect the outgoing webhook or Zapier sample. Confirm that the payload contains the field, then compare its name with the destination CRM property. My broader blank UTM field diagnosis follows the same boundary: find the last place where the value is present, then fix the next handoff.
Caching is the first infrastructure layer I check
A Contact Form 7 page can look normal while its hidden attribution fields are stale.
The native default:get method is evaluated while the server renders the form. If a page cache does not vary by the query string, it can reuse HTML that was created without the current visitor’s UTMs.
A persistent tracking setup has a different risk. The form HTML may be fine, but an aggressive cache or CDN can ignore the query args or cookies that the attribution layer needs.
I do not start by replacing Contact Form 7. I check the cache behavior. My WordPress hosting and UTM tracking guide shows the layers I inspect: host cache, CDN, Varnish, WordPress cache plugin, query-string rules, and cookie exclusions.
- Purge the page cache after changing the form.
- Test with a unique campaign value that has never been used before.
- Check the response headers to see whether the page came from cache.
- Verify the input value in the rendered DOM, not only the visible form.
- Repeat on an untagged second page if the setup is supposed to persist attribution.
How I verify UTM parameters in Contact Form 7
- Open a tagged URL with unmistakable values:
?utm_source=cf7_qa&utm_medium=manual&utm_campaign=contact_form_test_202608. - Inspect the hidden input in the browser and confirm its value before submitting.
- Submit with a unique email address so the test is easy to find.
- Open the email notification and confirm source, medium, and campaign separately.
- If Flamingo is active, open the saved inbound message and confirm the same fields.
- If a webhook or Zapier sends the lead onward, inspect the run data before checking the CRM.
- Open the CRM record and confirm the mapped destination fields.
- If persistence is expected, repeat the test after navigating to an untagged page.
I also test one direct or untagged visit. A good setup should not invent campaign values when no campaign data exists.
If you are choosing a form plugin rather than fixing an existing Contact Form 7 setup, my WordPress form plugin lead-tracking audit compares that broader decision. This article assumes you already use CF7 and want the handoff to work.
What I would not over-claim
- A populated hidden field proves the value reached the form. It does not prove the CRM stored it.
- An email notification is not a submission archive. Use Flamingo or another storage layer if you need a database record.
- A webhook does not fix missing attribution. It only moves the values it receives.
- The native
default:getmethod does not preserve UTMs across pages. - A persistence plugin does not override consent choices or a cache that is configured to ignore the required query strings and cookies.
- Contact Form 7 is not automatically the best or worst form plugin for attribution. The implementation decides whether the data survives.
My final checklist
- Use a current CF7 hidden form tag.
- Choose current-URL capture or persistent capture deliberately.
- Match every Form-tab field name to its Mail-tab tag.
- Add the field to the webhook, Zapier step, or CRM mapping separately.
- Inspect caching before blaming the form plugin.
- Verify the rendered hidden input before submit.
- Verify email, storage, integration payload, and CRM as separate checkpoints.
- Repeat the test after navigating away from the tagged landing page.
The form is not finished when it says “sent.” It is finished when the same campaign value reaches every place that depends on it.




Hi, thanks for this great resource. How easy would it be to pass these values through an API JSon Post Method?
We use a CRM system called Hubsolv and require to retain these values in a their system a. for sales/customer rapport building b. More important to me – so we can drill down and show that it’s not just about cheap leads, it’s about showing how well the data converts – build our decisions around ROAS than CPL!
I’ve been able to employ a freelancer to push the form fields of first name, last name, telephone and email address – but he doesn’t understand what I mean when I talk about utm values.
It’s probably the way I’m explaining to him!
Hi Craig,
In order for me to answer your question properly, I need to know more about your current implementation… Are you using CF7 as your optin form? Please consider joining our Slack channel and DM me for better communication https://www.haktansuren.com/slack-handlwp
Hello!
Is there any way to utilize the UTM field to track organic traffic? For example, if a customer types in our URL and it doesn’t have a UTM in the querystring/URL, the source will come back as a certain value?
Hi Kent, Unfortunately, UTM is not appended to the URL for organic traffic by Google. So, there is no way to capture that information. However, I highly recommended you to check the shortcodes like handl_original_ref etc in HandL UTM Grabber here
How do we print these in thank you page url as parameters.
https://test.com/thank-you-subscribing/?utm_source=%5Butm_source%5D&utm_medium=%5Butm_medium%5D&utm_campaign=%5BButm_campaign%5D Syntax didn’t work.
Using Contact Form 7 Redirection plugin to send users to thank you page.
So was asking if any tips or how to on this one.
Not sure if I understand your question but you can use the shortcodes on thank you page to print the parameters.
Is it possible to turn those parameters to contact form 7 legit fields so i will able to use them to send info to CRM as Hubspot?
because if they are shown in the email but not defined as contact form custom fields i can’t send them easily to CRM.
Hey,
Was wondering how to capture the URL Referrer in CF7. I have been successful at capturing UTM parameters set by a tracking template on Google Ads but can’t manage to use the shortcode for the referrer in CF7.
Where should I input the codes in CF7?
Thanks
Thomas
Tried the [handl_original_ref] shortcode?
Yes I did. Placed the [handl_original_ref] in the input section but I don’t know what to put in the body of the email.
The shortcodes used for the utm input are different, I tried to replicate the procedure but wasn’t successful.
Hi! nice job!!
It is possible ad this utm to a google sheets?
Campaign Source:
[utm_source]
and
Campaign Medium:
[utm_medium]
Thanks!
Very soon :). Next release I’ll implement Zapier integration. Using zapier you can send the data to Google Sheets as well
Please go ahead and upgrade to the latest. And then you can use Zapier to add UTMs to Google Sheets. See this blog post as well https://www.haktansuren.com/zapier-for-contact-form-7-utms-lead-tracking-step-by-step
[…] Based on the several feedback I received from the people using HandL UTM Grabber. Contact Form 7 is one of the major WordPress form plugins used to collect leads. Hence, In previous blog post, I explained how to capture UTMs from your Contact Form 7. […]
Hi,
I use your plugin. It works well and is very useful. I also use it and outside of Contact Form 7, but after version (2.6.3) it no longer works . So I went back to this version. I test the new versions from time to time, but the problem remains with me and I am forced to stay on version (2.6.3).
Maybe it needs some my syntax change already.
Here is part of this code where it is embedded.
$html .=”UTM Campaign:”.do_shortcode(‘[utm_campaign]‘).””;
This still works perfectly, but if I switch to a higher version of the plugin, instead the result, itself name of appears the shortcode.
But it continues to work well in Contact Form 7.
The results is the same regardless of the version of WP
Thanks.
[…] and want a practical attribution workflow, these existing guides on my site are worth reading too: Contact Form 7 UTM tracking and Zapier for Contact Form 7: UTMs & lead […]
[…] you use Contact Form 7 with HandL UTM Grabber, make sure the hidden fields are actually receiving […]
[…] I would not over-engineer the first version. The same practical logic applies whether you are using Contact Form 7, WPForms, Gravity Forms, a booking tool, or a CRM handoff through something like Zapier for Contact […]
[…] is why I care so much about hidden fields and form integrations. Whether you use Contact Form 7, Gravity Forms, WPForms, Formidable, WooCommerce, a booking tool, or a CRM embed, the conversion […]