How to Detect Contact Form 7 on Any Website (API Guide)

July 17, 2026 · 9 min read

Short answer: Contact Form 7 stamps wpcf7 all over the rendered page — in form container classes, input classes, hidden fields, and a global JavaScript object — and loads its assets from /wp-content/plugins/contact-form-7/. You can spot it with curl -s https://example.com | grep -o wpcf7 | head -1, or get a structured JSON answer from the free /demo endpoint with no API key. The rest of this guide covers every fingerprint and how to scale detection across a whole list of sites.

Contact Form 7 is one of the oldest and most widely installed WordPress plugins in existence — it has shipped with a huge share of WordPress builds since 2007 and still sits on millions of active installs. That ubiquity makes it a useful detection target: finding Contact Form 7 on a site confirms the site runs WordPress, tells you the owner relies on a free, developer-configured form rather than a paid form SaaS, and gives agencies and lead-gen teams a concrete conversation starter.

What Is Contact Form 7 and Why Detect It

Contact Form 7 (often abbreviated CF7, and prefixed wpcf7 in code) is a free WordPress plugin that manages multiple contact forms with Ajax-powered submission, CAPTCHA support, and Akismet spam filtering. It is deliberately minimal: no drag-and-drop builder, no hosted dashboard, just shortcodes and markup. That minimalism is exactly why it shows up everywhere from hobby blogs to large corporate sites.

Reasons teams detect it:

Manual Ways to Detect Contact Form 7

Contact Form 7 renders directly into the page HTML and loads its own script and stylesheet, so it leaves three distinct fingerprints you can check by hand.

Check the Page Source for wpcf7 Markers

Every form the plugin renders is wrapped in a container whose class and ID both carry the wpcf7 prefix, and the inputs inside it use wpcf7-form-control classes:

<div class="wpcf7" id="wpcf7-f123-p45-o1">
  <form action="/contact/#wpcf7-f123-p45-o1" method="post" class="wpcf7-form init">
    <input type="text" name="your-name"
           class="wpcf7-form-control wpcf7-text wpcf7-validates-as-required">
    ...
  </form>
</div>

On the page that hosts the form, a one-liner is enough:

$ curl -s https://example.com/contact/ | grep -o "wpcf7" | head -1
wpcf7

One caveat: this marker only appears on pages that actually render a form. The homepage of a CF7 site may show nothing while /contact/ lights up, so a homepage-only grep can produce false negatives.

Look for the contact-form-7 Plugin Path in Assets

WordPress serves plugin assets from a predictable path, and Contact Form 7 is no exception. Any page that enqueues the plugin's script or stylesheet references it directly:

<link rel="stylesheet"
      href="https://example.com/wp-content/plugins/contact-form-7/includes/css/styles.css?ver=5.9.8">
<script src="https://example.com/wp-content/plugins/contact-form-7/includes/js/index.js?ver=5.9.8"></script>

Two things make this the strongest manual signal. First, the /wp-content/plugins/contact-form-7/ path is unambiguous — no other software serves files from it. Second, WordPress appends the plugin version as a cache-busting ?ver= parameter, so the version number (here 5.9.8) rides along for free. This script URL is precisely the fingerprint DetectZeStack matches, and the ?ver= value is what populates the version field in the API response.

$ curl -s https://example.com | grep -o "plugins/contact-form-7[^\"']*"
plugins/contact-form-7/includes/css/styles.css?ver=5.9.8
plugins/contact-form-7/includes/js/index.js?ver=5.9.8

Inspect Network Requests for the wp-json/contact-form-7 REST Route

Since version 5.4, Contact Form 7 submits forms over the WordPress REST API. Open DevTools, go to the Network tab, submit the form, and you will see a POST to a route like:

POST /wp-json/contact-form-7/v1/contact-forms/123/feedback

The route namespace contact-form-7/v1 is another dead giveaway. You can also probe it without submitting anything — the plugin registers the namespace in the site's REST index:

$ curl -s https://example.com/wp-json/ | grep -o "contact-form-7/v1" | head -1
contact-form-7/v1

This works even on sites where no page currently renders a form, though some hardened WordPress installs disable or restrict /wp-json/ entirely.

Why Manual Detection Breaks at Scale

All three manual checks work for one site. They stop working the moment you have a list:

An API call solves all three at once: one request per site, structured JSON out, and the full technology stack in the same response.

Detect Contact Form 7 with the DetectZeStack API

The DetectZeStack API runs its full fingerprint database against a URL — HTML source, script URLs, DOM structure, JavaScript globals, headers, and DNS — and returns everything it finds as structured JSON. Contact Form 7 is fingerprinted by its script path (with version capture), its wpcf7 JavaScript global, and the plugin stylesheet link in the DOM.

Try It Free with the Demo Endpoint

The public /demo endpoint runs the same detector pipeline with no authentication. It is IP rate-limited, but perfect for a quick check or for confirming the response shape before you integrate:

curl -s "https://detectzestack.com/demo?url=https://example.com" | python3 -m json.tool

Full Detection with /analyze

For production use, sign up on RapidAPI and call /analyze with your key. When Contact Form 7 is present, the response looks like this:

curl -s "https://detectzestack.p.rapidapi.com/analyze?url=https://example.com" \
  -H "X-RapidAPI-Key: YOUR_KEY" \
  -H "X-RapidAPI-Host: detectzestack.p.rapidapi.com"
{
  "url": "https://example.com",
  "domain": "example.com",
  "technologies": [
    {
      "name": "Contact Form 7",
      "categories": ["WordPress plugins", "Form builders"],
      "confidence": 100,
      "description": "Contact Form 7 is an WordPress plugin which can manage multiple contact forms. The form supports Ajax-powered submitting, CAPTCHA, Akismet spam filtering.",
      "website": "https://contactform7.com",
      "icon": "Contact Form 7.png",
      "source": "http",
      "version": "5.9.8",
      "cpe": ""
    },
    {
      "name": "WordPress",
      "categories": ["CMS", "Blogs"],
      "confidence": 100,
      "description": "WordPress is a content management system.",
      "website": "https://wordpress.org",
      "icon": "WordPress.svg",
      "source": "http",
      "version": "",
      "cpe": ""
    }
  ],
  "categories": {
    "WordPress plugins": ["Contact Form 7"],
    "Form builders": ["Contact Form 7"],
    "CMS": ["WordPress"]
  },
  "meta": { "status_code": 200, "tech_count": 14, "scan_depth": "full" },
  "cached": false,
  "response_ms": 1842
}

A few things worth noticing. Contact Form 7 is classified under two categories — WordPress plugins and Form builders — so it appears twice in the top-level categories map, which lets you filter by either angle. The version field is parsed from the ?ver= parameter on the plugin's script URL when present. And WordPress itself shows up in the same response, because the same page that betrays the plugin fires WordPress's own fingerprints too.

To narrow the output to just the Contact Form 7 entry, pipe through jq:

curl -s "https://detectzestack.p.rapidapi.com/analyze?url=https://example.com" \
  -H "X-RapidAPI-Key: YOUR_KEY" \
  -H "X-RapidAPI-Host: detectzestack.p.rapidapi.com" \
  | jq '.technologies[] | select(.name == "Contact Form 7")'

Yes/No Checks with /check for a Single Technology

If all you want is a boolean, the /check endpoint answers the narrower question “does this site run this specific technology?” directly. The tech parameter is case-insensitive:

curl -s "https://detectzestack.p.rapidapi.com/check?url=https://example.com&tech=Contact%20Form%207" \
  -H "X-RapidAPI-Key: YOUR_KEY" \
  -H "X-RapidAPI-Host: detectzestack.p.rapidapi.com"
{
  "domain": "example.com",
  "technology": "Contact Form 7",
  "detected": true,
  "confidence": 100,
  "version": "5.9.8",
  "categories": ["WordPress plugins", "Form builders"],
  "response_ms": 1421,
  "cached": false
}

When the technology is not found, detected comes back false with a confidence of 0. This shape is convenient for pipelines that just need to branch on a boolean — no array filtering required.

Detecting Contact Form 7 Across Many Sites with /analyze/batch

Checking one site is useful; checking a list is where this earns its keep. The POST /analyze/batch endpoint accepts up to 10 URLs per request and scans them concurrently:

curl -X POST "https://detectzestack.p.rapidapi.com/analyze/batch" \
  -H "X-RapidAPI-Key: YOUR_KEY" \
  -H "X-RapidAPI-Host: detectzestack.p.rapidapi.com" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [
      "example.com",
      "wordpress.org",
      "contactform7.com",
      "example.org"
    ]
  }'

To turn a domain list into a clean list of Contact Form 7 sites, loop in batches of 10 and filter each result:

import requests

API_URL = "https://detectzestack.p.rapidapi.com/analyze/batch"
HEADERS = {
    "X-RapidAPI-Key": "YOUR_KEY",
    "X-RapidAPI-Host": "detectzestack.p.rapidapi.com",
    "Content-Type": "application/json",
}

def chunk(items, size=10):
    for i in range(0, len(items), size):
        yield items[i:i + size]

domains = ["example.com", "wordpress.org", "contactform7.com"]
cf7_sites = []

for batch in chunk(domains):
    resp = requests.post(API_URL, json={"urls": batch}, headers=HEADERS)
    for item in resp.json().get("results", []):
        result = item.get("result")  # None if that URL errored
        if not result:
            continue
        for tech in result.get("technologies", []):
            if tech["name"] == "Contact Form 7":
                cf7_sites.append((result["domain"], tech.get("version", "")))

print("Sites using Contact Form 7:", cf7_sites)

Each URL in a batch counts as one request against your monthly quota, so a 10-URL batch consumes 10 requests. For the full pattern — rate-limit handling, retries, and CSV output — see How to Batch-Scan 1,000 Websites.

What Contact Form 7 Tells You About a Website's Stack

It Confirms WordPress and Hints at Plugin-Heavy Builds

Because Contact Form 7 cannot run outside WordPress, a positive detection is also a CMS detection — and its /wp-content/plugins/ asset path is itself a WordPress signature. If your real question is “what CMS is this?”, start with how to detect what CMS a website uses instead; if you already know it is WordPress, the CF7 signal refines the picture:

Signal CombinationWhat It Suggests
Contact Form 7 alone A lean, developer-configured build — the site owner favors free, minimal tooling over form SaaS
Contact Form 7 + Elementor A classic agency-built small-business site; likely more plugins under the hood
Contact Form 7 + Yoast SEO An owner investing in organic search but not yet in marketing automation
Contact Form 7 + managed WP hosting Budget for infrastructure exists; form tooling is a plausible upsell conversation

All of these combinations come back in a single /analyze response, since the same scan surfaces page builders, SEO plugins like Yoast, analytics tools, and hosting signals like WP Engine alongside the form plugin. For sales teams, that means one API call per domain produces the whole qualification picture, not just a yes/no on the form.

One honest caveat: version detection depends on the ?ver= parameter surviving to the rendered page. Some caching and optimization plugins strip or rewrite query strings on static assets, in which case Contact Form 7 is still detected via its other fingerprints but the version field comes back empty.

Get Your API Key

Everything above runs on the free tier: 100 requests per month, no credit card. Sign up on RapidAPI, grab your key, and the /analyze, /check, and /analyze/batch examples in this guide work as-is with your key substituted in.

Conclusion

Contact Form 7 is one of the easiest technologies on the web to detect: it stamps wpcf7 into its markup, serves assets from an unambiguous /wp-content/plugins/contact-form-7/ path with the version in the URL, and registers a contact-form-7/v1 REST route. Manual checks with curl and DevTools work fine for a single site. For anything more — a prospect list, a plugin audit across client sites, a security inventory — the DetectZeStack API turns the same fingerprints into structured JSON, adds the rest of the tech stack in the same response, and scales to thousands of domains through batching.

Related Reading

Try DetectZeStack Free

100 requests per month, no credit card required. WordPress, plugin, CMS, analytics, and infrastructure detection on every plan.

Get Your Free API Key

Get API updates and tech detection tips

Join the mailing list. No spam, unsubscribe anytime.