Dynamic URL Parameters for Personalization: Pre-Fill Forms and Swap Page Copy From One Link

Two people click the same link. One sees “Project management for agencies” with their work email already filled in. The other sees “Project management for engineering teams” and an empty form.
Same URL. Same landing page. Different experience.
That happens because the link carried instructions, and the page read them. Most writing on this topic covers the first half: how the parameter gets into the link. Almost nobody documents what the destination page does with the value once it arrives.
That second half is where the real wins and the real disasters both live.
One thing up front, so this post stays in its lane. I am not covering how ad platforms auto-fill your parameters. That is a separate job with separate syntax, and I already wrote it: let the ad platform fill them in has the Meta, Google Ads and TikTok macro tables. If you need the field-type model of which values to lock and which to automate, that is in UTM templates. And if you are still hand-typing ? and &, start with how to add URL parameters.
This post assumes the parameter already arrived. Now what?
Why the destination side is where personalization actually happens
A URL parameter is a key and a value passed in the query string, after the ?. Analytics reads it. That is the part everyone knows.
Here is the part most teams miss. The parameter is sitting in the browser’s address bar, readable by any JavaScript on the page, which means your landing page can use it to change itself before the visitor reads a single word.
Look at the mechanics:
https://example.com/demo?utm_campaign=agencies&industry=agency&email=priya%40studio.co
utm_campaign goes to GA4 and stops there. But industry and email are just data sitting in the URL. Nothing consumes them automatically. Your page has to choose to.
A parameter your page ignores is an analytics tag. A parameter your page reads is a personalization instruction.
The honest limitation: this only works when the visitor arrives through your link. Someone typing your domain directly, or arriving from organic search, brings nothing. Every parameter-driven personalization has to degrade to a sensible default, and I have watched more campaigns break on a missing default than on anything else.

The three things a landing page can do with a parameter value
There are exactly three, and they differ wildly in effort and risk.
| Action | What it does | Effort | Risk | Reversible? |
|---|---|---|---|---|
| Render | Prints the value as visible copy (headline, subhead, button) | Low | High if unsanitized | Yes, instantly |
| Pre-fill | Drops the value into a form field, visible or hidden | Low | Medium, tampering and privacy | Yes, instantly |
| Branch | Picks a different section, offer, or layout based on the value | High | Low | Yes, but slower |
Render is dynamic text replacement. The page swaps a headline to match what the visitor searched or which ad they clicked.
Pre-fill is the quiet workhorse. The value lands in a form field so the visitor types less, or in a hidden field so your CRM learns where the lead came from.
Branch is the heaviest and the most durable. Instead of printing the value, you use it to select from content you wrote in advance. industry=agency shows an agency case study, industry=engineering a different one. Nothing user-controlled reaches the page, which is why branching carries the lowest risk.
My opinion, and it is a strong one: most teams reach for render when they should be branching. Rendering a raw value is fast to build and feels impressive in a demo. Branching takes an afternoon and cannot be exploited. If the personalization matters commercially, branch.
Honest limitation: branching does not scale past a handful of variants. Five industries is fine. Five hundred keywords is not, and that is exactly where render earns its place.
Dynamic text replacement is not dynamic keyword insertion
These two get conflated constantly, including in most of the articles ranking for this topic right now. They are different mechanisms running in different places, and mixing them up leads to real broken campaigns.
| Dynamic keyword insertion (DKI) | Dynamic text replacement (DTR) | |
|---|---|---|
| Where it runs | Inside the ad, on the ad platform | On your landing page, in the browser |
| What it changes | Ad headline or description | Page copy |
| Who controls it | Google Ads | You |
| Triggered by | The matched keyword | A URL parameter |
| Fallback | Ad platform default text | Your default value |
| Fails how | Shows generic ad text | Shows blank or default copy |
DKI happens before the click. DTR happens after it. Run both and the ad and the page finally say the same thing, which is the entire point.
The page-side mechanic is simple. A tool like Unbounce reads a named parameter and swaps a marked block of text:
https://example.com/lp?kw=project%20management%20for%20agencies
The page finds kw and prints it into the headline slot, so the visitor sees copy matching the search that brought them. Unbounce, Instapage and Landingi ship this natively. On WordPress you use a plugin or write it yourself, which is about six lines.
I want to be careful with reported results, because the numbers here are almost all vendor and agency reported, not independent research. Unbounce and agency case studies commonly report 10% to 20% conversion lift on paid campaigns using DTR. One published dynamic-versus-static test reported 31.4% higher conversion with over 100 conversions per variant. Vendor analyses also attribute 60% to 70% of the total lift to the headline alone. Treat all of these as directional. They come from people selling the technique.
Honest limitation: DTR matches words, not meaning. If your keyword list contains cheap project management and you pipe it straight into a headline, your page now says “cheap” in your own voice. I have seen this go live. Filter your inputs, or branch instead.

Pre-filling forms from the URL, and the two fields to never touch
Pre-filling works because most form tools already look for it. In HubSpot, if a query-string key matches a field’s internal property name, the value populates that field automatically, and this works for both visible and hidden fields.
So this URL:
https://example.com/demo?email=priya%40studio.co&industry=agency&utm_source=newsletter
drops the email into the email field, the industry into an industry field, and the source into a hidden field your CRM keeps forever. The visitor sees a form already half done, and completion goes up for the obvious reason: less typing.
Hidden fields are the higher-value half. A hidden field is a form input the visitor never sees, submitted along with everything else. It is how campaign data crosses from the URL into the CRM record, so months later you can ask which campaign produced the deal, not just the click.
Now the part that gets skipped.
Never pre-fill a price, amount, quantity, discount, or plan tier from a URL parameter. If the page puts ?price=499 into a field and your server trusts what comes back, someone will send ?price=1 and you will honor it. This is parameter tampering, it is trivial, and it is not hypothetical. The rule is simple: a value that affects money must be looked up server-side from an ID, never accepted from the URL.
Be careful pre-filling email. Not because of tampering, but because a pre-filled email address means anyone who gets that forwarded link can submit a form as that person. Newsletter forwards, shared Slack links and screenshots all leak it. For a low-stakes content download, fine. For anything that changes an account, no.
Related, if you are tempted to put identifiers in parameters at all: I covered why URLs are exceptionally leaky for personal data in custom UTM parameters beyond the standard five. Referrer headers, server logs and pasted screenshots all carry the full query string.
Honest limitation: pre-filled fields get trusted and left alone. If your parameter is stale or wrong, the visitor will submit it without reading. Pre-filling raises completion and lowers data accuracy at the same time. Decide which one you need more.

The fallback value is the whole feature
Here is the thing nobody tells you until it breaks. Personalization built on URL parameters is not defined by what happens when the parameter is present. It is defined by what happens when it is absent.
Four cases, and your page will meet all four in week one:
- The parameter is missing. Someone typed your domain, came from organic search, or clicked a link where the parameter got stripped. This is the common case, not the edge case.
- The parameter is present but empty.
?kw=resolves to nothing. A macro that did not fire leaves exactly this. If your code checks only for the key existing, you will print an empty headline. - The parameter carries a value you never planned for. A forwarded link, an old campaign, or someone editing the URL for fun.
- The link got shared. The parameter was accurate for the original recipient and is now wrong for everyone downstream.
Every DTR tool handles case 1 with a default value, and Unbounce’s documentation is explicit that default text is what appears when no parameter value is present. But case 2 catches people constantly, because an empty string is not the same as a missing key.
The check you need:
const params = new URLSearchParams(window.location.search);
const kw = (params.get('kw') || '').trim();
const headline = kw !== '' ? kw : 'Project management that fits your team';
document.querySelector('#headline').textContent = headline;
That || ' and the .trim() are the difference between a working page and a blank headline in production. Note textContent, not innerHTML. That is the next section.
My rule: write the page so it reads perfectly with zero parameters, then treat every parameter as an enhancement. If the personalized version is the only version that makes sense, the default is wrong and the page is broken for most of your traffic.
Honest limitation: a good fallback hides failure. If your macro silently stops firing, the default keeps the page looking fine while your personalization quietly does nothing. Check a live tagged URL monthly. Nothing will alert you.
What personalized parameters do to your SEO
Start here, because a lot of advice on this is simply out of date.
Google retired the Search Console URL Parameters tool on 26 April 2022, announced on 28 March 2022 on the Search Central blog. The stated reason: only about 1% of the parameter configurations site owners had specified were actually useful for crawling. Google said no action was required and its crawlers would handle parameters automatically from then on.
I still see articles published in 2026 telling readers to “configure parameter handling in Search Console”. That control has not existed for over four years. If you are following that advice, you are configuring nothing.
So what actually governs it now?
- Canonical tags. Point every personalized variant at the clean URL. Critically, a canonical tag does not prevent crawling. Googlebot still fetches the URL, reads the hint, then decides whether to honor it. Canonicals manage indexing, not crawl.
- robots.txt disallow. The only blunt instrument left for parameter patterns you never want fetched. Use it carefully, because a blocked URL cannot pass its canonical hint either.
- Not linking to parameter URLs internally. The simplest and most underused lever. If nothing links to it, it mostly does not get crawled.
The canonical you want on a personalized page:
<link rel="canonical" href="https://example.com/demo" />
Google’s crawl budget documentation, updated 22 July 2026, states that every site starts with the same conservative default crawl capacity, raised over time when there is demand and the site stays healthy. Google has also named faceted navigation as one of the most common sources of duplicate or near-duplicate content in large crawls, and personalization parameters create the same shape of problem: many URLs, near-identical content.
The practical read: if you run a 40-page site, parameter variants will not hurt you. If you run 40,000 URLs and add three personalization parameters that combine freely, you have invented a large number of near-duplicate URLs worth controlling.
Honest limitation: client-side DTR is largely invisible to this problem, because the parameter changes what renders, not what is served. That sounds like good news. It also means Google may index the default version of a page you think is personalized. If the personalized copy matters for ranking, it belongs in the HTML, not in JavaScript.

The security line most teams cross without noticing
This is the section I most want people to read, because the mistake is one line of code and the consequence is a live vulnerability on a page you are actively buying traffic to.
Taking a URL parameter value and assigning it to innerHTML is the textbook reflected cross-site scripting vulnerability. Reflected XSS means an attacker crafts a URL containing a script, sends it to a victim, and the page obediently executes it in that victim’s browser, in your site’s context.
The difference is exactly this:
// Dangerous. The browser parses the value as HTML and will run script it finds.
document.querySelector('#headline').innerHTML = params.get('kw');
// Safe. The value is inserted as text and never parsed as markup.
document.querySelector('#headline').textContent = params.get('kw');
textContent treats the value as characters. innerHTML treats it as code. Per MDN and the OWASP cross-site scripting guidance, the fix is to remove unsafe sinks like innerHTML and use textContent or value, apply context-aware output encoding, or use the Trusted Types API to force input through a sanitizer.
Why does this keep happening on marketing pages? The code gets written in a hurry by someone building a campaign, not an application, pasted into a tag manager, and never reviewed. The page works. Nobody looks again.
A short checklist I apply to any parameter-driven page before it takes spend:
- Every parameter value goes in through
textContentor a form fieldvalue, neverinnerHTML. - Values that drive branching are checked against a list you wrote, not used directly.
- No parameter value ever reaches a
href,src, or inline event handler. - Money, quantity and entitlement values come from the server, never the URL.
- Open the page with a deliberately silly parameter value and confirm it renders as plain text.
Honest limitation: textContent protects the page, not your brand. It will happily print whatever someone puts in the URL, as text, in your headline. Anyone can craft that link and screenshot it. If a page prints user-controlled copy, branching from an approved list is the only version that is safe in both senses.
Frequently Asked Questions
What is dynamic text replacement?
Dynamic text replacement (DTR) is a landing page feature that swaps visible copy based on a value passed in the URL. The page reads a named parameter and prints its value into a marked text block, most commonly the headline, so the page matches the ad or search that brought the visitor. Every implementation requires a default value for visitors who arrive without the parameter.
What is the difference between dynamic text replacement and dynamic keyword insertion?
Dynamic keyword insertion runs inside the ad on the ad platform and changes the ad’s own headline based on the matched keyword. Dynamic text replacement runs on your landing page and changes page copy based on a URL parameter. DKI happens before the click, DTR happens after it. They are often used together so the ad and page carry the same message, but they are separate systems.
Can I pre-fill a form field using a URL parameter?
Yes, and most major form tools support it out of the box. In HubSpot, a query-string key that matches a field’s internal property name populates that field automatically, for both visible and hidden fields. Hidden fields are the more valuable use, because they carry campaign data from the URL into the CRM record. Never pre-fill a price, discount, quantity or plan tier this way, since a visitor can edit the URL and your server may trust the result.
What happens if the URL parameter is missing or empty?
The page shows whatever default you defined, and if you did not define one it shows a blank. Missing and empty are two different failure cases: a visitor arriving from organic search has no parameter key at all, while a macro that failed to fire leaves the key present with an empty value. Code that only checks whether the key exists will print an empty string in the second case. Check for both, and write the page so it reads correctly with no parameters at all.
Is it safe to display a URL parameter value on the page?
Only if you insert it as text rather than as markup. Assigning a parameter value to innerHTML creates a reflected cross-site scripting vulnerability, because the browser parses the value as HTML and executes any script inside it. Using textContent inserts the value as plain characters and neutralizes it. Even then the page will display whatever someone puts in the URL, so for anything commercially sensitive, use the parameter to select from approved content rather than printing it directly.
Do personalized landing page URLs need a canonical tag?
Yes, if those URLs are reachable and crawlable. Point each parameter variant at the clean version of the page so search engines consolidate them rather than treating them as near-duplicates. Remember that a canonical tag manages indexing, not crawling: Googlebot still fetches the URL before reading the hint. Google retired the Search Console URL Parameters tool on 26 April 2022, so canonical tags, robots.txt and not linking to parameter URLs internally are the levers that remain.
Where to start
Pick one page and one parameter. Not five.
Write the page so it reads perfectly with no parameters. Add a single parameter that changes one thing, give it a real default, insert it with textContent, and add a canonical tag. Ship that, watch it for two weeks, then add the second parameter.
The teams that get value from this treat the parameter as an enhancement to a page that already works. The teams that get burned build a page that only makes sense when the parameter is present, then find that most of their traffic does not carry it.
If your personalization depends on campaign parameters arriving intact, build the links properly in the first place with the free UTM builder at linkutm.