How to Detect jQuery Migrate on Any Website (API Guide)

September 2, 2026 · 11 min read

jQuery Migrate is the compatibility shim that keeps ten-year-old jQuery code running on a modern jQuery build. It is loaded by default on every WordPress front end, by a long tail of legacy themes, and by enterprise portals that were "temporarily" bridged to a newer jQuery years ago and never went back. Its presence tells you something jQuery alone does not: the site is carrying code written for a jQuery API that no longer exists.

This guide covers the fingerprints jQuery Migrate leaves in HTML and in the browser, a console check for a single page, and the DetectZeStack API for detecting it across thousands of domains with the version parsed out. Every JSON example uses the fields the API actually returns, and the two worked examples are real responses from live sites.

What jQuery Migrate Is and Why Sites Still Ship It

The jQuery team removed a batch of deprecated APIs in jQuery 1.9 (2013) and another batch in jQuery 3.0 (2016). To stop those releases from breaking the web, they published jquery-migrate: a plugin that patches the removed methods back in and, in its development build, logs a JQMIGRATE warning to the console every time old code calls one. Two major lines exist:

The intended workflow is: upgrade jQuery, load Migrate, fix everything it warns about, remove Migrate. In practice step three rarely finishes. WordPress core enqueues Migrate on every front-end page under the jquery-migrate script handle, so most of the WordPress web ships it without anyone deciding to. Themes and plugins that were written against jQuery 1.x keep working because of it, and nothing breaks loudly enough to force a cleanup.

Why Detect jQuery Migrate

jQuery is on a majority of the public web, so "uses jQuery" is a weak filter. "Uses jQuery Migrate" is sharper, because Migrate exists only to keep obsolete code alive. The teams who ask for it:

Each of those wants the same two answers: is Migrate present? and which version? The version matters because 1.x and 3.x mean different things, which the pitfalls section covers.

How jQuery Migrate Leaves Fingerprints on a Page

Migrate advertises itself in two independent ways: a script tag in the HTML, and a set of properties it attaches to the jQuery object at runtime. The first is visible to any HTTP client; the second needs a browser.

The jquery-migrate.min.js Script Tag and the WordPress ?ver= Parameter

Migrate is nearly always loaded as its own file, immediately after jQuery core, and the filename says what it is. The common shapes:

Where it loads fromTypical srcVersion location
WordPress core/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1?ver= query string
Official jQuery CDNcode.jquery.com/jquery-migrate-3.4.1.min.jsfilename
Public CDNs (cdnjs, jsDelivr).../jquery-migrate/3.4.1/jquery-migrate.min.jsURL path
Self-hosted, unversioned/assets/js/jquery-migrate.min.jsnone

WordPress adds two extra tells. Since WordPress 5.5 every enqueued script gets an id attribute derived from its handle, so the tag reads <script id="jquery-migrate-js" src="...">. And WordPress appends the version of the enqueued asset as ?ver=, which is why the Migrate version on a WordPress site lives in the query string rather than the filename. A plain grep over the HTML catches all of the above:

curl -sL https://gridpane.com | grep -oE '<script[^>]*jquery-migrate[^>]*>'

On that site the result is a single tag:

<script id="jquery-migrate-js" src="https://gridpane.com/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.4.1" ...>

The jQuery.migrateVersion and jQuery.migrateWarnings Globals

Once it executes, Migrate decorates the jQuery object. The properties that matter for detection:

Because these are runtime properties, they only exist inside a browser that has executed the page's scripts. An HTTP fetch of the HTML cannot see them, which is why the API-side detection in the next sections works from the script tag instead.

Manual Detection in the Browser Console

For a single page, open DevTools and paste this in the console. It reports presence, version, and whether the page has already tripped any compatibility patches:

(function () {
  var jq = window.jQuery;
  if (typeof jq !== 'function') { console.log({ jquery: false }); return; }
  var migrate = typeof jq.migrateVersion === 'string';
  console.log({
    jquery: jq.fn.jquery,
    migrate: migrate,
    migrate_version: migrate ? jq.migrateVersion : null,
    warnings: migrate && jq.migrateWarnings ? jq.migrateWarnings.length : 0
  });
})();

A WordPress site on current core prints something like:

{ jquery: "3.7.1", migrate: true, migrate_version: "3.4.1", warnings: 0 }

If warnings is greater than zero, run jQuery.migrateWarnings on its own to read the list. Each entry names the removed API that was called, such as jQuery.fn.load() is deprecated or jQuery.browser is deprecated. That list is the to-do list for removing Migrate from the site.

Migrate without jQuery is impossible. Migrate is a plugin, not a standalone library, so a positive Migrate detection always implies jQuery is present even if the jQuery script itself was bundled somewhere you did not spot. The DetectZeStack fingerprint encodes this: a jQuery Migrate match adds jQuery to the technologies array if it was not already there.

Detect jQuery Migrate at Scale With the DetectZeStack API

The console approach does not survive contact with a domain list. The DetectZeStack /analyze endpoint fetches the page over HTTP, runs the fingerprint set against the headers and HTML, and returns every detected technology as JSON. jQuery Migrate is reported as its own entry in the technologies array, in the JavaScript libraries category, with the version parsed from the script src when one is present.

Try It Free With GET /demo Before Getting a Key

The public /demo endpoint runs the same detector with no authentication. It is rate-limited per IP address, so it is for confirming the response shape rather than for scanning, but it needs nothing beyond curl:

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

The response is one JSON object. Trimmed to the entries this article cares about, the live response for that domain looks like this:

{
  "url": "https://gridpane.com",
  "domain": "gridpane.com",
  "technologies": [
    {
      "name": "WordPress",
      "categories": ["CMS", "Blogs"],
      "confidence": 100,
      "description": "WordPress is a free and open-source content management system written in PHP and paired with a MySQL or MariaDB database. Features include a plugin architecture and a template system.",
      "website": "https://wordpress.org",
      "icon": "WordPress.svg",
      "source": "http",
      "version": "",
      "cpe": "cpe:2.3:a:wordpress:wordpress:*:*:*:*:*:*:*:*"
    },
    {
      "name": "jQuery",
      "categories": ["JavaScript libraries"],
      "confidence": 100,
      "description": "jQuery is a JavaScript library which is a free, open-source software designed to simplify HTML DOM tree traversal and manipulation, as well as event handling, CSS animation, and Ajax.",
      "website": "https://jquery.com",
      "icon": "jQuery.svg",
      "source": "http",
      "version": "",
      "cpe": "cpe:2.3:a:jquery:jquery:*:*:*:*:*:*:*:*"
    },
    {
      "name": "jQuery Migrate",
      "categories": ["JavaScript libraries"],
      "confidence": 100,
      "description": "Query Migrate is a javascript library that allows you to preserve the compatibility of your jQuery code developed for versions of jQuery older than 1.9.",
      "website": "https://github.com/jquery/jquery-migrate",
      "icon": "jQuery.svg",
      "source": "http",
      "version": "3.4.1",
      "cpe": ""
    }
  ],
  "categories": {
    "CMS": ["WordPress"],
    "Blogs": ["WordPress"],
    "JavaScript libraries": ["jQuery", "jQuery Migrate"]
  },
  "meta": { "status_code": 200, "tech_count": 22, "scan_depth": "full" },
  "cached": false,
  "response_ms": 1423
}

Three things to notice. The Migrate entry is separate from the jQuery entry. Its version is 3.4.1, lifted from the ?ver= query string shown in the grep earlier. And source is http, meaning the match came from the fetched HTML rather than from DNS or TLS signals.

curl Example Against GET /analyze and the technologies Array

For real usage, sign up on RapidAPI and call /analyze with your key. The shape is identical to /demo; the difference is that requests count against your monthly plan instead of the per-IP demo limit. Piping through jq isolates the Migrate entry:

curl -s "https://detectzestack.p.rapidapi.com/analyze?url=https://gridpane.com" \
  -H "x-rapidapi-key: $RAPIDAPI_KEY" \
  -H "x-rapidapi-host: detectzestack.p.rapidapi.com" \
  | jq '.technologies[] | select(.name == "jQuery Migrate")'

When Migrate is present that prints the single matching object:

{
  "name": "jQuery Migrate",
  "categories": ["JavaScript libraries"],
  "confidence": 100,
  "description": "Query Migrate is a javascript library that allows you to preserve the compatibility of your jQuery code developed for versions of jQuery older than 1.9.",
  "website": "https://github.com/jquery/jquery-migrate",
  "icon": "jQuery.svg",
  "source": "http",
  "version": "3.4.1",
  "cpe": ""
}

When it is absent, jq prints nothing and exits zero, which makes the command safe to drop into a shell loop. If you only need a yes/no answer for one domain, the /check endpoint does the filtering server-side and returns a flat object:

curl -s "https://detectzestack.p.rapidapi.com/check?url=gridpane.com&tech=jQuery%20Migrate" \
  -H "x-rapidapi-key: $RAPIDAPI_KEY" \
  -H "x-rapidapi-host: detectzestack.p.rapidapi.com"
{
  "domain": "gridpane.com",
  "technology": "jQuery Migrate",
  "detected": true,
  "confidence": 100,
  "version": "3.4.1",
  "categories": ["JavaScript libraries"],
  "response_ms": 1380,
  "cached": false
}

Reading the version Field: Script Filename vs ?ver= Query String

The Migrate fingerprint matches a script src of the form jquery-migrate (or jquery.migrate), optionally followed by -<version>, optionally .min, then .js, optionally followed by ?ver=<version>. Two capture groups feed the version field:

  1. The version embedded in the filename, as in jquery-migrate-3.4.1.min.js.
  2. The version in a trailing ?ver= query string, as in jquery-migrate.min.js?ver=3.4.1.

If both are present the filename wins. If neither is present, version is an empty string and only name tells you Migrate is there. The ?ver= fallback is what makes WordPress sites report a version, because WordPress never puts the version in the filename.

Filtering for a specific major line in saved responses is one jq expression. This flags every saved scan still on the 1.x series, and treats a missing version as unknown rather than as 1.x:

jq -r '
  .technologies[]
  | select(.name == "jQuery Migrate")
  | (.version // "") as $v
  | if $v == "" then "\(input_filename): jQuery Migrate (version unknown)"
    elif ($v | split(".")[0] | tonumber) < 3 then "\(input_filename): jQuery Migrate \($v) (1.x line)"
    else empty end
' analyses/*.json

Batch Detection Across Thousands of Domains With POST /analyze/batch

For lists, /analyze/batch accepts up to 10 URLs per request and scans up to 5 of them concurrently on the server. Each URL still counts as one request against your plan, so the batch endpoint is about wall-clock time, not quota. The request body is a JSON object with a urls array:

curl -s -X POST "https://detectzestack.p.rapidapi.com/analyze/batch" \
  -H "x-rapidapi-key: $RAPIDAPI_KEY" \
  -H "x-rapidapi-host: detectzestack.p.rapidapi.com" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["gridpane.com", "wptavern.com", "example.com"]}' \
  | jq -r '
      .results[]
      | select(.result != null)
      | . as $r
      | ($r.result.technologies[] | select(.name == "jQuery Migrate")) as $m
      | "\($r.url)\t\($m.version // "")"
    '

The response has a results array with one item per submitted URL. Each item carries either a result object in the same shape as a single /analyze response or an error string, plus top-level successful, failed, and total_ms counters. The jq above prints one tab-separated line per domain where Migrate was found, with the version or an empty column. For the three domains above the output is two lines, because example.com loads no JavaScript at all:

gridpane.com	3.4.1
wptavern.com	

To run a file of a thousand domains, chunk it into tens and feed each chunk to the same call. A short bash loop that writes one JSON line per batch:

#!/usr/bin/env bash
# migrate-batch.sh — usage: migrate-batch.sh domains.txt > results.jsonl
set -euo pipefail
KEY="${RAPIDAPI_KEY:?set RAPIDAPI_KEY}"
HOST="detectzestack.p.rapidapi.com"

split -l 10 "$1" chunk_
for f in chunk_*; do
  body=$(jq -R -s -c 'split("\n") | map(select(length > 0)) | {urls: .}' "$f")
  curl -s -X POST "https://${HOST}/analyze/batch" \
    -H "x-rapidapi-key: ${KEY}" \
    -H "x-rapidapi-host: ${HOST}" \
    -H "Content-Type: application/json" \
    -d "$body" \
  | jq -c '.results[] | {
      url: .url,
      error: (.error // null),
      migrate: ((.result.technologies // [])[] | select(.name == "jQuery Migrate")) // null
    }'
  rm -f "$f"
done

Every output line is {"url":...,"error":null,"migrate":{...}} when Migrate was found, "migrate":null when the scan succeeded without it, or a non-null error when the fetch failed. Keep the error rows; a domain behind aggressive bot protection returns no HTML, and dropping it silently would turn "could not scan" into "does not use Migrate." For concurrency, retries, and quota planning across a larger list, see Batch Scan 1,000 Websites for Tech Stack.

Common Pitfalls

Four situations regularly produce a wrong count. Each one is visible in the data if you know to look.

Migrate Has a Version but jQuery Does Not

Look back at the gridpane.com response: Migrate reports 3.4.1 while the jQuery entry's version is empty, even though the page loads jquery.min.js?ver=3.7.1. The jQuery fingerprint reads the version from the filename or URL path, not from a ?ver= query string, so WordPress-style jQuery tags yield a name without a version. Do not treat the missing jQuery version as a scan failure, and do not infer the jQuery version from the Migrate version. They are separate packages on separate release schedules.

Migrate Without a Version

Sites that serve WordPress core assets through a CDN can strip the query string. wptavern.com loads https://c0.wp.com/c/7.1/wp-includes/js/jquery/jquery-migrate.min.js, where 7.1 in the path is the WordPress version, not the Migrate version. The API correctly reports jQuery Migrate with an empty version. An empty version means "present, version not visible from this signal," never "absent."

Migrate 1.x vs 3.x Mean Different Things

Migrate 3.x on top of jQuery 3.x is the default state of a maintained WordPress site and says little about the theme. Migrate 1.x is a much stronger signal: it only makes sense with jQuery 1.9 through 2.x, so its presence means the site never moved to jQuery 3 at all. WordPress stopped shipping Migrate 1.4.1 in version 5.5 (2020) and moved to the 3.x line in 5.6, so a WordPress site reporting Migrate 1.x is either years behind on core or has a theme that enqueues its own copy. Filter on the major version, not just on presence.

Bundled Builds

A build pipeline that concatenates jQuery and Migrate into one vendor.js leaves no jquery-migrate in any script src. The HTTP-layer detector cannot see inside the bundle, so the API will not report Migrate for those sites. The runtime jQuery.migrateVersion check in the browser still works, because Migrate attaches its properties regardless of how it was delivered. If a specific site matters, confirm it in the console before concluding it is clean.

Related JavaScript Detection

Migrate rarely appears alone, and the same /analyze response that surfaces it names everything else on the page. Follow-ups worth checking in the same result:

Get Your API Key and Start Detecting

  1. Confirm the response shape with curl "https://detectzestack.com/demo?url=https://gridpane.com". No key required.
  2. Sign up at rapidapi.com/mlugoapx/api/detectzestack and copy your x-rapidapi-key. The free tier is 100 requests per month with no credit card.
  3. Run the /analyze example above against a domain you care about and read the jQuery Migrate entry's version.
  4. Move to /analyze/batch when the list grows past a handful of domains.

Conclusion

jQuery Migrate is a compatibility shim, so detecting it is detecting technical debt directly. On one page, jQuery.migrateVersion in the console answers both questions in a second. Across a portfolio, the jQuery Migrate entry in the DetectZeStack technologies array answers them per domain, with the version parsed from either the filename or the WordPress ?ver= string. Read the major version, treat an empty version as unknown rather than absent, and remember that a Migrate hit always means jQuery is there too.

Related Reading

Detect jQuery Migrate and Every Other Library in One API Call

One HTTP request returns every framework, library, CDN, CMS, and analytics tag on a page, with version strings where the source reveals them. 100 requests per month free. No credit card.

Get your free API key

Get API updates and tech detection tips

Join the mailing list. No spam, unsubscribe anytime.