A client rings us on a Monday: “We got eleven enquiries through the website last week. Which campaign produced them?” They want to know whether the Google Ads spend is working, whether that LinkedIn push paid off, or whether half of it was just organic traffic that would have arrived anyway. So we open their Fluent Forms entries, and every single submission says the same thing about lead source: nothing. Name, email, message. No campaign, no channel, no clue.
This happens constantly, and it is not because Fluent Forms is a bad plugin. It is genuinely one of the best WordPress form builders going. It just was not designed to be an attribution tool, and the default setup throws away the very data you need to answer the most important question in marketing: where did this lead actually come from? In this guide we will walk through the three real ways to capture UTM parameters in Fluent Forms, including the native option most people reach for first, why it quietly loses most of your data, and the approach we eventually built for ourselves.
TL;DR — three ways to add lead source to Fluent Forms
- Native hidden fields — add Hidden fields and use “Populate by GET Param”. Free, no code, but only catches UTMs present in the URL at the exact moment the form page loads. Loses navigation, caching and direct/organic traffic.
- JavaScript + cookies — a script reads UTMs on landing, stores them in a cookie, and writes them into hidden fields later. Survives navigation, but breaks under Safari/ITP and still misses organic and direct.
- Server-side capture — a plugin like Lead Source records the source on the first request, server-side, and attaches it to every submission automatically. No hidden fields, survives caching and ITP, and fills in organic and direct too.
Why Fluent Forms doesn’t track lead source by default
Fluent Forms is a form builder. Its job is to collect the fields you put on the form, validate them, store the entry and fire off your notifications and integrations. It does that brilliantly. What it does not do out of the box is look at how the visitor arrived and quietly record that alongside the submission. There is no built-in “lead source” column in your entries, because the plugin has no opinion about where your traffic comes from.
That means the campaign data riding in the URL — the ?utm_source=google&utm_medium=cpc string after someone clicks an ad — is simply ignored unless you explicitly tell a field to capture it. And as we will see, the most common way of telling it to do that is far more fragile than people realise. The information you most want is the information Fluent Forms is least set up to keep.
What “lead source” actually means
Lead source is the set of clues that tell you how a person reached your form. It usually includes UTM parameters (utm_source, utm_medium, utm_campaign, utm_term, utm_content) that you tag campaign links with; ad click IDs like gclid and fbclid that platforms append automatically; the referrer (the site they came from); and the landing page (the first page they hit on your site).
There is also a time dimension. First touch is the campaign that originally discovered the lead; last touch is whatever they clicked just before converting. Good attribution keeps both, because the ad that found someone and the email that closed them are rarely the same thing.
The three methods at a glance
- Method 1 — Native hidden fields: Fluent Forms’ built-in “Populate by GET Param” reads UTMs straight from the URL into hidden fields. Free, no code, but limited.
- Method 2 — JavaScript and cookies: a custom script captures the source on landing and persists it across pages before pushing it into hidden fields.
- Method 3 — Server-side capture: a plugin records the source on the very first server request and attaches it to the entry automatically, with no fields to configure.
Method 1 — Native dynamic hidden fields in Fluent Forms
Fluent Forms can pull a value out of the URL and drop it into a field, no extra plugin required. This is the method nearly every tutorial points you towards, and to be fair it works in the free version. Here is the accurate, current process.
- Open your form in the Fluent Forms editor (Fluent Forms → All Forms → Edit).
- From the Advanced Fields group in the input panel, drag a Hidden field onto the form. Add one Hidden field per UTM you want to capture.
- Click the field to open its settings and set its Name Attribute to match the parameter — for example
utm_source,utm_medium,utm_campaign. - Open Advanced Options, then click the small dropdown / three-dot icon beside the Default Value box and choose Populate by GET Param.
- Enter the URL parameter name to read from (e.g.
utm_source) so the field fills itself from the query string when the page loads. - Save the form. Test by visiting it with a tagged URL such as
?utm_source=newsletter&utm_campaign=spring, submit, then check the result under Fluent Forms → Entries.
For form fields you also want filled, the same “Populate by GET Param” option appears on standard inputs like Email, so you can pre-fill those from the URL too. The values then save into the entry and travel through any FluentCRM or webhook integration you have wired up.
Works when: the visitor lands directly on the form page with all UTM parameters still in the URL, on an uncached page. Breaks when: they browse to another page first, the page is served from cache, a browser strips the parameters, or they arrived with no UTMs at all (organic or direct).
Why hidden fields lose most of your lead source data
This is the section that matters most, because hidden fields fail silently. They do not error. They just submit blank, and you do not notice until you are staring at a month of empty lead-source columns wondering where the data went. There are four separate ways this method leaks, and most sites suffer from all of them at once.
Navigation loss. “Populate by GET Param” only reads the URL of the page the form sits on. Someone clicks your ad, lands on the homepage with the UTMs attached, has a click around, then submits a form on the contact page — which has a clean URL with no parameters. The hidden field reads that clean URL and saves nothing. This is the single biggest cause of blank fields, and it describes a completely normal visit.
Page caching. Populating from a GET param relies on PHP running when the page loads. Cache plugins and host-level caching (WP Rocket, LiteSpeed, Cloudflare and friends) serve pre-built static HTML without running that PHP, so the default value never gets injected and the field stays empty. The faster your site, the more reliably this breaks.
Safari and ITP. Any approach that leans on the URL persisting, or on client-side storage holding it, runs into Apple’s Intelligent Tracking Prevention and similar privacy features that clamp down on cross-site parameters and cap script-set cookies. On a big slice of mobile traffic the trail is gone before your form ever sees it. We dig into this failure mode in our guide on why UTM parameters disappear in WordPress.
Organic and direct show blank. UTMs only exist on links you tagged. A visitor from a Google organic result, a forwarded link, or someone typing your domain in directly carries no UTMs at all — so the hidden field has nothing to read. That can easily be half your leads recorded as a void, when in reality “organic search” is itself a perfectly good answer the method simply cannot give you.
Method 2 — JavaScript and cookies
The usual fix for the navigation problem is JavaScript. You add a script that runs on landing, reads the UTMs from the URL, and stashes them in a cookie or localStorage. Later, when the visitor finally reaches a form, a second piece of script reads that storage and writes the values into hidden fields just before they submit. This is exactly how tools like HandL UTM Grabber and various GTM recipes operate, and it is a real step up from Method 1 because the source now survives the journey from landing page to contact page.
It is not free of problems, though. Script-set cookies are precisely what Safari’s ITP caps to a short lifetime, so a visitor who lands today and converts next week may come back empty. The script has to fire before the form is filled, which render-blocking and consent banners can interfere with. And it still cannot manufacture data that was never there: organic and direct visitors remain blank, because there were no UTMs to capture in the first place. You have plugged one of the four leaks, not all four.
Verdict — Works when: visitors convert within a short window on browsers that respect long-lived cookies. Breaks when: Safari/ITP shortens the cookie, consent or caching delays the script, or the visit had no UTMs to begin with.
Method 3 — server-side capture with Lead Source
Here is the mechanism first, because it is the whole point. On a visitor’s very first request to your site, before any page is cached and before any browser privacy feature gets involved, the request itself arrives at your server carrying everything: the full URL with its UTMs and click IDs, the referring site, and the landing page. Lead Source reads all of that server-side at that moment and writes a first-party attribution record into your WordPress database, tied to that visitor.
From then on it does not matter how many pages they browse, whether the contact page is cached, or whether Safari strips anything afterwards — the record already exists. When the visitor eventually submits a Fluent Forms form, Lead Source hooks the submission save and attaches the stored source to that entry automatically. There are no hidden fields to add, no Name Attributes to match, and no GET-param options to configure. It keeps both first touch and last touch, so you see the campaign that originally found the lead and whatever they clicked last.
Because the capture happens server-side on the first request, it sidesteps every leak in Method 1: navigation loss, page caching and ITP simply do not apply. And crucially, it does not return blank for organic and direct — it records the referrer and landing page, so “came from a Google organic result” or “landed directly on the pricing page” becomes real, usable data. We built this in-house at our own ad agency precisely because we were tired of the blank columns, and we go deeper on the approach in our guide to tracking lead source in WordPress forms.
What server-side capture still can’t fix
Honesty matters here, so the caveat gets its own heading. Server-side capture reads what reaches the server. If a parameter is stripped from the URL before the request ever arrives — by an aggressive redirect chain, a link wrapper that drops query strings, or a privacy tool that scrubs the URL client-side first — then nothing server-side can recover it, because it never made it to the server. No technique can reconstruct data that was destroyed before transmission. What server-side capture does is eliminate every loss that happens after the request lands, which is where the overwhelming majority of real-world leakage occurs.
Capturing ad click IDs
UTMs are only half the story. Ad platforms append their own click identifiers automatically, and these are what let you match a lead back to a specific click inside the ad account — and increasingly what offline conversion imports require. The ones worth catching are gclid, gbraid and wbraid (Google Ads), fbclid (Meta), msclkid (Microsoft Ads), li_fat_id (LinkedIn) and ttclid (TikTok).
You can technically chase each one with another hidden field and another GET-param setting, but that inherits every weakness of Method 1 — and click IDs are even more prone to navigation loss because they only appear on the ad-click URL, never on internal links. Server-side capture grabs them on the first request alongside the UTMs, so they survive. If you are feeding conversions back into ad platforms, our guide to tracking ad click IDs in WordPress covers why this matters for offline conversion uploads.
Comparing the three methods
| Capability | Native hidden fields | JavaScript + cookies | Server-side (Lead Source) |
|---|---|---|---|
| Setup effort | Low (no code) | High (custom code) | Low (install plugin) |
| Survives page navigation | No | Yes | Yes |
| Survives page caching | No | Partly | Yes |
| Survives Safari/ITP | No | No | Yes |
| Records organic / direct | No | No | Yes |
| Captures ad click IDs reliably | Weak | Partly | Yes |
| Keeps first & last touch | No | Rarely | Yes |
| Hidden fields to maintain | One per parameter | One per parameter | None |
Which method should you use
| Your situation | Best method |
|---|---|
| One landing page, no caching, quick conversions, just testing | Native hidden fields |
| Comfortable maintaining scripts, mostly desktop audience, short sales cycle | JavaScript + cookies |
| Real ad spend you need to justify, multi-page site, mobile traffic | Server-side (Lead Source) |
| You want organic and direct leads attributed too, not just paid | Server-side (Lead Source) |
| You run a cache plugin or CDN (WP Rocket, LiteSpeed, Cloudflare) | Server-side (Lead Source) |
| You feed offline conversions back to Google or Meta | Server-side (Lead Source) |
Setup checklist
- Decide which leads you need to attribute — paid only, or organic and direct as well.
- Confirm whether your site runs a cache plugin or CDN (most do — this rules out reliable hidden-field capture).
- List the parameters you care about: the five UTMs plus the click IDs for the platforms you actually advertise on.
- Choose your method using the matrix above.
- If going server-side, install Lead Source and confirm it is hooking your Fluent Forms submissions.
- Run a real test: visit via a tagged ad-style URL, browse to another page, then submit, and check the entry actually shows the source.
- Test on Safari mobile specifically — it is where the weaker methods fall over.
- Decide where the data needs to flow: Fluent Forms entries, FluentCRM, or an external CRM, and check it carries through.
Frequently asked questions
Does Fluent Forms track UTMs by default?
No. Fluent Forms stores the fields you place on the form and nothing about how the visitor arrived. To capture UTMs natively you have to add Hidden fields and switch on “Populate by GET Param” for each one. Even then it only reads the URL of the form page itself, so it captures far less than people expect.
How do I pull URL values into a Fluent Forms field?
Open the field’s settings, expand Advanced Options, click the dropdown beside the Default Value box and choose Populate by GET Param, then enter the parameter name (e.g. utm_source). Make sure the field’s Name Attribute matches. This works in the free version of Fluent Forms.
Why are my hidden UTM fields coming through blank?
Almost always one of three reasons: the visitor navigated to another page before submitting so the form-page URL had no UTMs; the page is served from cache so the PHP that injects the value never ran; or the visitor arrived with no UTMs at all (organic or direct). All three produce silent blanks, which is exactly why server-side capture exists.
How do I attribute organic and direct leads?
Hidden fields and JavaScript can’t, because there are no UTMs on those visits. Server-side capture records the referrer and landing page instead, so an organic Google visit or a direct type-in becomes a real, labelled source rather than a blank. That often turns out to be half of your leads. Multi-touch context matters too — see our piece on multi-touch attribution and conversion windows.
Will this slow my site down?
Server-side capture is light: it reads the incoming request and writes a small record, with no render-blocking script in the browser and nothing to slow the page paint. In fact, because it does not depend on PHP running on a cached form page, it actually plays nicely with aggressive caching — which is the opposite of the native hidden-field approach that caching tends to break.
Does this work with GDPR and my CRM?
Lead Source stores first-party attribution data in your own WordPress database rather than shipping it to a third party, which keeps you in control of it for GDPR purposes — handle it under your privacy policy as you would any other lead data. Because the source attaches to the Fluent Forms entry itself, it travels through your existing notifications, FluentCRM, and webhook or CRM integrations like any other field.
Stop guessing where your leads come from
The native method is fine for a quick test, and JavaScript plugs one leak. But if you are spending real money on ads and need to answer “which campaign produced this lead?” without staring at blank columns, the only approach that holds up across navigation, caching, Safari and organic traffic is capturing the source server-side on the first request.
That is exactly what we built Lead Source to do. It is a free WordPress plugin, it hooks Fluent Forms automatically with no hidden fields to configure, and it keeps both first and last touch in your own database. Install it, run one tagged test, and you will finally see where your leads actually come from.

Leave a Reply