How to track lead source in Contact Form 7 and WPForms
A WordPress contact form gives you a name, an email and a message. It does not give you the Google Ads campaign that paid for the click, the search that found your blog post, or the referral site that sent them. That information exists in the browser at the moment they land. Nothing in Contact Form 7 or WPForms reads it, and by the time the email hits your inbox it is gone.
The fix is the same in both plugins: hidden fields on the form, filled in before submit. What differs is the syntax and where the values end up.
What “lead source” actually means
Before touching a form, decide what you want on the record. Most people want four things:
Channel. Paid search, organic search, paid social, email, referral, direct. This is the level you make budget decisions at.
Campaign and keyword. Specific enough to act on. “Paid search” tells you to keep spending. “The emergency-plumber campaign, on the keyword emergency plumber near me” tells you where.
Landing page. Which page brought them in. Often the most useful field for content work, and the one most tracking setups leave out.
First touch as well as last touch. Someone reads a blog post in March, comes back in April through a branded search, and fills in the form. Last touch says organic brand. First touch says the blog post. Both are true, and only recording one of them will send your budget to the wrong place.
Contact Form 7
CF7 posts exactly the fields you declare in the form template. Add hidden ones and they get posted too.
Add the hidden fields
Open Contact > Contact Forms, edit your form, and paste hidden field shortcodes anywhere in the template. They render nothing, so the layout does not shift.
[hidden lead_channel]
[hidden lead_source]
[hidden lead_campaign]
[hidden lead_keyword]
[hidden landing_page] Add them to the email
This is the step people miss. CF7 does not store submissions. It emails them, and if a field is not in the email template the value is captured and then thrown away.
Open the Mail tab and add the tags to the message body:
Channel: [lead_channel]
Source: [lead_source]
Campaign: [lead_campaign]
Keyword: [lead_keyword]
Landing page: [landing_page] If you want the data stored in WordPress as well, install Flamingo. It is from the same author, it stores every CF7 submission in the admin, and hidden field values show up alongside the name and message, so you can search past enquiries by campaign without opening a CRM.
A CF7 warning
Field names in the shortcode and the Mail tab must match character for character. [hidden lead-source] in the form and [lead_source] in the mail template produces an email with the literal text [lead_source] in it, which is the most common symptom of a broken setup.
WPForms
WPForms Pro has a Hidden Field type under Fancy Fields. Drag one onto the form, click it, and put the field name in the Label box. Leave Default Value empty, because whatever you type there will be submitted instead of the real value.
Repeat for each field. Then check the Advanced tab: WPForms tracks fields by numeric ID internally, but the label is what gets passed to notification emails, Zapier and CRM integrations. The label is the name that matters.
Submissions land in WPForms > Entries with the hidden values listed beside the visible ones.
WPForms Lite
Lite has no Hidden Field type. Use the HTML field instead and paste raw inputs:
<input type="hidden" name="lead_channel">
<input type="hidden" name="lead_source">
<input type="hidden" name="lead_campaign">
<input type="hidden" name="lead_keyword">
<input type="hidden" name="landing_page"> This is functionally identical. Anything that fills form fields works on the name attribute, and an HTML field produces a real input with a real name.
Where the values come from
Empty hidden fields are not much use. Something has to write into them, and there are a few levels of effort here.
The cheapest is a few lines of JavaScript that copies utm_source and friends out of location.search into the matching inputs. It’s nearly free to build, and it works for anyone who lands directly on the form page from a tagged link.
It breaks the moment someone browses. Land on a service page, read it, click Contact, and the query string is gone. It also gives you nothing for organic search, referrals or social, because none of those arrive with UTM parameters.
A step up from that is writing the UTM values into a first-party cookie on the first page view and reading them back at the form. That survives navigation and return visits, and it’s where a homegrown solution starts turning into a project: you have to handle document.referrer for untagged traffic, decide what counts as a new session, and keep first touch separate from last touch.
Beyond that you’re really building your own small tracking tool: same hidden fields, same forms, someone on your team maintaining the edge cases indefinitely.
Whichever route you take, one thing is worth knowing before you build it. Safari limits JavaScript cookies to seven days, and outside the EU, Chrome, Firefox and Edge on iOS all run on the same WebKit engine and behave identically. If your typical lead takes longer than a week to convert, first touch data will be missing for a large share of your traffic unless the cookie is set by your server rather than by JavaScript. On WordPress that means PHP setting the cookie on page load, which also means excluding pages with forms from full-page caching so the PHP actually runs.
Getting it from the form to your CRM
WordPress forms rarely stop at an email. If yours pushes to HubSpot, Pipedrive, ActiveCampaign or a spreadsheet, three things have to line up:
- A field exists in the destination.
- A hidden field exists on the form.
- The connection in the middle maps one to the other.
Point three is where most setups fail silently. Everything looks configured and the field arrives empty. Open the mapping screen in your form’s CRM add-on or your Zap and check every field by name.
Test it before you trust it
Open an incognito window and visit a page with the form using a tagged URL:
https://yoursite.com/contact?utm_source=google&utm_medium=cpc&utm_campaign=test&utm_term=emergency+plumber Before you submit, open dev tools, find the <form> element, and confirm the hidden inputs already hold values. If they are empty at this point, nothing downstream will save you.
Submit it. Check the notification email, then check WPForms > Entries or Flamingo, then check the CRM record. Each of those three is a separate place the data can be lost, so check all three rather than assuming.
Then do it again on a phone. And once more after clearing your cache, because a page cached before you added the tracking script will not have it.
Two useful details
Popups and multi-step forms are fine. Hidden fields can sit on any step, and a form that appears after a delay still works as long as whatever fills the fields watches the page for new forms rather than only checking once on load.
Empty is not the same as missing, either. A direct visit genuinely has no campaign, and writing “(not set)” into the field rather than leaving it blank makes that distinction visible. It saves an hour of debugging a setup that was working correctly.
If you would rather not build it
SourceTag fills the hidden fields on any WordPress form: Contact Form 7, WPForms, Gravity Forms, Elementor and the rest. It handles untagged traffic by referrer, keeps first click and last click separately, and its WordPress plugin sets the cookie from PHP so Safari keeps it for 400 days instead of seven. The field reference lists every value it can write.
