How to Pre-Populate a Contact Form 7 Field from a URL Parameter
Every CTA button on your site is a purchase intent signal. When a visitor clicks “Get a Quote” on your WooCommerce service page, they have already told you what they want — but by the time the contact form lands in your inbox, that context is gone. This post covers how we wired all our CTA buttons across services (WooCommerce, speed optimisation, WordPress SEO, Sage development) to pass a service identifier through the URL into the Contact Form 7 submission, and how we protected the site from the SEO side effects of parameterised URLs at the same time.
Part 1 — For Business Owners
The problem: enquiries arrive without context
When someone reads your speed optimisation page and clicks “Get a Quote”, they have already told you what they want. But a standard contact form throws that context away. By the time the email lands in your inbox you have a name, an email address, and a blank message — and you spend the first reply asking “which service were you interested in?”
In marketing terms, what you are missing is lead segmentation: the ability to categorise an incoming enquiry by the visitor’s stated intent before you ever reply. Without it, every lead looks identical in your inbox regardless of whether they came from your WooCommerce page, your SEO services page, or your speed optimisation case study.
Multiply that across every CTA on the site and you are adding unnecessary back-and-forth to every lead, delaying the first useful reply and creating friction at the bottom of the funnel — exactly where you can least afford it.
What we built: passive lead qualification across all services
Lead qualification is the process of determining whether a prospect is a good fit and what they need — before investing time in a conversation. Traditionally that means a dropdown on the form (“Which service are you interested in?”) or a multi-step form that routes enquiries. Both add friction and reduce completions.
This setup does it passively. Each CTA button across the site links to the contact page with a ?service= parameter in the URL. We have four live across different service areas:
- WooCommerce development — CTAs on WooCommerce tutorials and the WooCommerce service page link to
/contact/?service=woocommerce - Speed optimisation — CTAs on performance case studies link to
/contact/?service=performance-audit - WordPress SEO — CTAs on SEO posts and the SEO service page link to
/contact/?service=wordpress-seo - Sage / agency development — CTAs on Sage theme posts and developer-facing content link to
/contact/?service=sage-development
When the visitor lands on the contact page, that parameter is silently captured and attached to the form submission. The notification email includes a Service field with the value already filled in — before the visitor has typed a single character. For the visitor, nothing changes. The contact form looks and works exactly as before.
Why it matters for your business
- Faster, more relevant first reply: you know which service they want before you open the email, so your first response can quote the right scope, timeframe, and price range rather than asking a clarifying question.
- Passive lead segmentation: enquiries arrive pre-labelled. Over time you can see which service generates the most leads, which converts best, and where to invest more content effort — without any analytics setup.
- Intent-based attribution: the
?service=value reflects what the visitor was reading when they decided to get in touch — a stronger signal than a referrer URL or UTM parameter, because it captures the conversion intent at the moment of click. - No extra fields for the visitor: the service is captured invisibly — the form stays short and conversion-friendly, with no dropdown to scroll through or step to complete.
- No plugin subscription or analytics dashboard required: the tracking lives inside Contact Form 7 and arrives directly in your inbox. No Google Analytics, no CRM, no monthly fee.
Part 2 — For Developers
The full setup: four moving parts
The implementation has four parts: the CTA links, the CF7 form field, the Mail tab, and the SEO protection for the parameterised URLs. Each is simple on its own — the value is in combining them correctly.
1. CTA links with a service parameter
Each CTA block links to /contact/ with a ?service= query parameter identifying which service the visitor came from. We use consistent slugs across all four service areas:
/contact/?service=woocommerce ← WooCommerce dev CTAs
/contact/?service=performance-audit ← Speed optimisation CTAs
/contact/?service=wordpress-seo ← SEO service CTAs
/contact/?service=sage-development ← Sage / agency dev CTAs
Keep the values human-readable and map them directly to service areas. Notification emails then need no lookup table — Service: woocommerce is self-explanatory at a glance. Any new service area you add in future just needs a new CTA with a new ?service= value — the form handles it automatically with no code changes.
2. CF7 Dynamic Text Extension — capturing the parameter
Install the free CF7 Dynamic Text Extension plugin. It adds a [dynamictext] field type to Contact Form 7 that can read GET parameters, cookies, post meta, and other dynamic sources at render time.
In the CF7 form template, add the following field. It renders as a hidden input — no label visible to the visitor:
[dynamictext service "CF7_GET key='service'"]
The key detail: the syntax is CF7_GET key='service', not URL:service. The latter is an older convention from a different plugin and will produce a literal string instead of the parameter value.
Full form template for reference:
<label> Your name
[text* your-name autocomplete:name] </label>
<label> Your email
[email* your-email autocomplete:email] </label>
<label> Subject
[text* your-subject] </label>
<label> Your message (optional)
[textarea your-message] </label>
<label> Service
[dynamictext service "CF7_GET key='service'"] </label>
[submit "Submit"]
Caching caveat. The [dynamictext] field reads the GET parameter at PHP render time, so any page-level caching layer that ignores query strings will serve a stale (empty) value to subsequent visitors. Two cache layers are commonly confused here:
- Page cache (Nginx FastCGI cache, Cloudflare full-page cache, WP Super Cache, LiteSpeed) caches full HTML responses — this is the layer that matters. It must bypass on query strings, or the dynamic field breaks.
- Object cache (Redis, Memcached) caches database queries and transients, not HTML output. It does not affect
[dynamictext]at all — no config changes needed regardless of provider.
On Trellis (Bedrock + Roots), the standard Nginx template already includes if ($query_string != "") { set $skip_cache 1; }, so any URL with a parameter bypasses FastCGI cache automatically — no edit to cache.skip_cache_uri in wordpress_sites.yml required. Verify with:
curl -I https://yourdomain.com/contact/?service=woocommerce
Look for fastcgi-cache: BYPASS in the response headers. If you see HIT or MISS instead, your cache is being populated with parameterised URLs and you need to add a query-string bypass rule before this technique will work reliably.
On Cloudflare, the equivalent is making sure the Cache Rule for the contact page either excludes /contact/ or includes “Cache by Query String” with service in the param list (the latter would create one cache entry per service value — fine for a small set, wasteful at scale).
3. Mail tab — surfacing the value in notifications
In the CF7 Mail tab, add Service: [service] to the message body:
From: [your-name] [your-email]
Subject: [your-subject]
Message Body:
[your-message]
Service: [service]
--
This is a notification that a contact form was submitted on your website ([_site_title] [_site_url]).
Enable “Exclude a line from output if all of its mail-tags are blank” in the Mail tab. This ensures that when a visitor arrives at /contact/ directly — with no ?service= parameter — the Service: line is silently omitted from the email rather than appearing blank.
4. SEO protection — the part most tutorials skip
Adding ?service= parameters to internal links creates a new problem: Googlebot follows those links and treats each parameter variant as a potentially separate URL. Left unaddressed, /contact/?service=woocommerce and /contact/?service=sage-development appear in Google Search Console as “Alternate page with proper canonical tag” entries.
This is not theoretical — it happened on this site. Between April 21 and April 28, 2026, the “Alternate page with proper canonical tag” count in GSC jumped from 7 to 26 (+19). When we exported the list, 20 of the 26 URLs were CTA tracking parameters on /contact/. Google was actively following our own CTA links from service pages and indexing each variant as a near-duplicate.
Two things handle this:
robots.txt — block crawling of param URLs
Add a Disallow rule for the ?service= pattern. This prevents Googlebot from crawling the parameterised variants at all, keeping them out of the crawl budget entirely:
# Block CTA service parameter URLs (duplicate of /contact/, canonical handled)
Disallow: /*?service=
We use a static robots.txt file in the Bedrock site/web/ directory, which overrides the virtual file generated by The SEO Framework. This gives full control over the output without relying on plugin settings that can be reset on updates.
Canonical URL — consolidate any that slip through
Set the canonical URL on the /contact/ page explicitly to https://imagewize.com/contact/ — no parameters. In The SEO Framework this is the Canonical URL field under SEO Settings → General. If Google does encounter a parameterised URL via an external link or cached sitemap, the canonical tag consolidates all signals to the clean URL.
robots.txt blocks crawling; the canonical handles indexing. Together they eliminate the duplicate URL problem without any impact on users.
Testing
Visit each parameterised URL, submit the form, and verify the notification email arrives with the correct Service: value:
https://yourdomain.com/contact/?service=woocommerce
https://yourdomain.com/contact/?service=performance-audit
https://yourdomain.com/contact/?service=wordpress-seo
https://yourdomain.com/contact/?service=sage-development
Each submission should arrive with the correct Service: value in the notification. Direct visits to /contact/ with no parameter should arrive with the Service: line omitted entirely.
Verify the robots.txt rule is live with:
curl -s https://yourdomain.com/robots.txt | grep service
Expected output: Disallow: /*?service=
Need WordPress SEO Support for Your Business?
We handle WordPress SEO for SMEs — from technical foundations (schema, crawlability, Core Web Vitals) to on-page optimization and content strategy. Fixed-price audits and ongoing support available.
- Technical SEO audit and implementation
- Schema markup and structured data
- Core Web Vitals and page speed optimization
- On-page SEO and content strategy