gclid is missing from your landing page URL for one of four reasons: auto-tagging is off in Google Ads, a redirect dropped the query string, the browser stripped the parameter, or the click was an iOS click that got gbraid or wbraid instead. Only the first is a setting. The other three are plumbing, and each has a different fix.
Work them in that order. Most people start by rewriting the tracking template, which is almost never it.
What is gclid, and what is supposed to put it there?
gclid — Google Click Identifier — is an opaque string Google Ads appends to your final URL when someone clicks your ad. Auto-tagging is the account-level setting that appends it. It is the only deterministic link between a specific ad click and everything that happens on your site afterwards.
It is not a UTM. UTMs are strings you write; gclid is issued by Google and only Google can decode it. That matters when it goes missing, because you can regenerate a UTM by hand and you cannot regenerate a click ID. If the distinction is still fuzzy, gclid vs fbclid vs UTM parameters covers what each one is for.
Why is gclid missing from my landing page URL?
Four causes account for nearly every case. They fail in different places — the ad account, your server, the browser, and the click itself — which is why one blanket “fix” rarely works.

Is auto-tagging turned off in Google Ads?
Auto-tagging is a per-account checkbox. If it is off, Google Ads never appends the click ID and there is nothing to lose downstream. Turn it on and it applies to clicks from that point forward — it is not retroactive, so yesterday’s leads stay click-less forever.
The trap: a manager account can hold a different setting from the child account that owns the campaign. Check the one that owns the campaign.
Is a redirect dropping the query string?
This is the most common cause on real sites. A 301 or 302 that rebuilds the destination URL from a template — rather than passing the original query string through — discards ?gclid=… in the process. The usual suspects: http:// → https:// upgrades, www. → apex canonicalisation, trailing-slash normalisation, language or country redirects, a CMS “pretty URL” rule, a page-builder’s domain-level redirect, or a link shortener sitting in front of your landing page.
Chains of three hops are normal, and the parameter only has to be dropped once.
Is the browser stripping the parameter?
Several browsers now remove known click-tracking parameters from URLs before the page loads, in specific modes — strict tracking-protection settings and private browsing being the usual ones. Content blockers do the same with “remove URL parameter” rules. The effect is identical to a bad redirect: the request arrives without a click ID, and nothing in your stack ever sees one.
Was there ever a gclid, or was it gbraid or wbraid?
On iOS traffic where the user is not identifiable, Google issues gbraid or wbraid instead of gclid. Your landing page URL is not empty — it has a different parameter than the one your code is looking for. If your capture logic reads gclid and nothing else, that traffic looks untracked when it is fully tracked.
This is not a bug to fix, it is a parameter to add: capturing gbraid and wbraid covers storing them and using them for offline conversion import. If a big slice of your paid traffic is iOS, work through how to audit your iOS attribution gap first.
Did your own page erase it after loading?
The URL arrives intact and something on the page rewrites it. Single-page-app routers call history.replaceState on the first route resolution. Cookie banners reload the page. Consent tools bounce the visitor through a consent domain and back. The tell is an address bar that changes on its own a moment after load — and anything reading the URL after that point, including a tag that loads late, gets the rewritten version.
Did it die at an iframe or a subdomain boundary?
An embedded form is a separate document with its own URL. A Typeform, Jotform or GoHighLevel widget never sees your query string — it was loaded from the form platform’s domain with the form platform’s parameters. Same story when the form lives on a subdomain or a second domain and nothing forwards the click ID across.
How do you tell which cause you have?
Open DevTools, tick Preserve log in the Network tab, click your own ad, and read the first document request. That single step separates all four causes:
- Never there on the first request → auto-tagging, tracking template, or browser stripping. Retest in a clean profile to split the last one out.
- There, then gone after a 30x → redirect. Read each hop’s
Locationheader; the hop whoseLocationhas no?is your culprit. - Survives every hop but your tag has nothing → a page-side rewrite, or a tag that initialises too late. Log
location.searchfrom the first inline script and compare. gbraidorwbraidwhere you expectedgclid→ iOS traffic, working as designed.
Use a real click, not the final URL pasted into the address bar — pasting skips the redirect behaviour you are trying to observe.
What does a missing click ID actually cost you?
Three things, and they compound.
Offline conversion import into Google Ads is keyed on a click ID. With none, the lead that became a sale three weeks later cannot be sent back to the account, so Smart Bidding never learns which clicks were worth buying.
Your reports reclassify the traffic. A session with no click ID and no UTM falls back to referrer, which is a large part of why ad conversions show as Direct in tools that only look at the converting session.
And the journey breaks. Someone clicks on a phone at lunch and buys on a laptop that evening; with no click ID in a durable record from the first visit, there is nothing to stitch the two together — the problem covered in tracking leads across devices. Booking businesses feel it hardest, since the outcome lands in a calendar tool days later: tracking booked appointments to Google and Meta Ads is the same problem with a longer gap.
What can you fix yourself, without any vendor?
All of it is fixable, with honest limits on each.
Turn auto-tagging on. One checkbox, and it applies going forward only — it does not affect clicks that already happened.
Make every redirect query-preserving. In Apache, QSA on the rewrite rule. In Nginx, append $is_args$args to the target. In Cloudflare Rules, tick preserve query string. In a page builder, check whether the domain redirect is configurable at all — often it is not, and that is the limit: you cannot fix a redirect you do not own.
Read the click ID once, immediately, and store it yourself. Before a router or a consent banner can rewrite anything:
// First inline script on the page, not the last.
const params = new URLSearchParams(location.search);
for (const key of ['gclid', 'gbraid', 'wbraid', 'gad_source']) {
const value = params.get(key);
if (!value) continue;
try { localStorage.setItem('pl_' + key, value); }
catch (e) { /* private mode or blocked storage */ }
}
The limit: browser storage is the weak link. Script-written cookies are capped on Safari, localStorage gets cleared, and private windows may refuse the write. Reading early solves the page-rewrite case, not the storage-lifetime case.
Store all three click IDs, and store which one you got rather than normalising gclid, gbraid and wbraid into one column — they are not interchangeable across Google’s surfaces.
Forward the click ID across boundaries yourself. Append it to the iframe src, or postMessage it into the frame and have the embedded form read it. It works, and it is brittle — every form vendor exposes a different hook, and they change.
How does PartialLeads keep the click ID from going missing?
PartialLeads reads the click ID out of the landing URL on the session’s first capture payload and stores it server-side — not in a browser cookie that later gets capped or cleared. gclid, gbraid, wbraid and gad_source are all captured, alongside fbclid, msclkid, ttclid and epik, plus the landing page and referrer.
Server-side storage is what defeats the page-rewrite case: once the value is posted, it does not matter that a router rewrote the address bar a half-second later, or that a consent banner reloaded the page. For the iframe case, the tag listens for postMessage attribution from parent frames and broadcasts UTMs down into embedded frames, with document.referrer as the fallback — that is what keeps a GoHighLevel or Typeform embed from producing a source-less lead.
Return visits stay attached because the visitor ID is a server-set first-party cookie, not subject to the seven-day cap Safari applies to cookies written by JavaScript. And when a conversion session genuinely has no click ID, two mechanisms can still resolve it: the click ID is one of the six tiers in the identity cluster, so sessions sharing a gclid are unioned into one person; and visit-sibling inheritance lets a session with no attribution inherit strong attribution from a sibling session in the same visit — same client, IP and user agent within ±30 minutes, with a cellular-handoff tier that allows the IP to change for a phone moving from wifi to LTE mid-checkout. A production example: a Shopify purchase session that read “Direct” was flipped to Google Ads because the buyer’s earlier same-visit session carried the gclid.
Then the conversion goes back: matched purchases and qualified leads are exported for Google Ads offline conversions with the click ID plus hashed email and phone — E.164 normalisation with a geo fallback, Gmail dot and plus-address normalisation before hashing.

Where you see it working: the Leads list shows each lead’s source and journey as a row of touch badges, so a lead that arrived on a Google Ads click reads that way at list resolution. The Attribution report’s Recovered Attribution card itemises what a raw last-click view would have lost, by mechanism — iOS ad-click rescue, where a first touch is re-linked to a lost ad click, and sessions reclassified by session intelligence, typically the largest bucket and mostly traffic that would otherwise read as Direct.
The honest limits. If auto-tagging never ran, no vendor recovers the click ID — it was never issued. If a server-side redirect dropped the query string before your landing page was requested, the tag never sees it either, because the tag runs on the page the browser actually loaded; that one you fix on your server. And sibling inheritance is an inference from your own session data, not a join Google has confirmed.
| What breaks | The mechanism | Where you see it in the dashboard |
|---|---|---|
| A router, consent banner or SPA rewrites the URL after load | Click ID read on the session’s first capture payload, stored server-side | Lead’s source and UTM columns |
| Form is an iframe with no query string | postMessage attribution relay into the frame, plus referrer fallback | Source label on the lead |
| Conversion session reads Direct although an earlier session had the gclid | Visit-sibling inheritance, ±30 min, plus the cellular-handoff tier; server-set visitor cookie keeps return visits stitched | Recovered Attribution → reclassified by session intelligence |
| iOS click issued gbraid or wbraid, not gclid | All click IDs stored per session, each under its own key | Recovered Attribution → iOS ad-click rescue |
| The lead never gets back into Google Ads | Offline conversion export: click ID plus hashed email and phone | Google Sheets dispatch, per-lead API column |
| A server-side redirect stripped it before your page loaded | None — nothing captures what the browser never received | Not applicable: this one is on your server |