navigator.plugins no longer lists real plugins - specs hard-code it now. Here's what an empty array, extra entries, or a pdfViewerEnabled mismatch reveal.
navigator.plugins used to be a real inventory: whatever Flash, Java, or Silverlight plugins a user had installed, listed out for any page to read. That inventory is gone. Recent versions of the specification hard-code the returned list — every browser that matches the spec now returns the same fixed set of entries, or none at all. That sounds like the end of the story for a once-popular fingerprinting surface. It isn't quite. A value that is supposed to be fixed becomes informative the moment it isn't fixed, or the moment it disagrees with a related API that describes the same capability.
Key Takeaways
- The plugin list is no longer real data. Recent spec versions hard-code
navigator.plugins: if inline PDF viewing is supported, it lists exactly five fixed entries; if not, it returns an emptyPluginArray. navigator.pdfViewerEnabledis the sanctioned way to check PDF support now — MDN explicitly says not to infer it fromnavigator.plugins.- The list still carries a detection signal, just not an identity one. An empty array on a browser claiming desktop Chrome, unexpected extra entries, or a mismatch between
navigator.pluginsandnavigator.pdfViewerEnabledare internal-consistency failures, not normal variation. - On its own,
navigator.pluginsnow has near-zero entropy — almost every real browser reports one of two possible states, so it can't distinguish one visitor from another the way canvas or font fingerprints can. Its remaining value is as a consistency check layered on top of other signals, not a standalone identifier. - This is a different surface from browser extensions. Extensions are user-installed add-ons detected through separate means;
navigator.pluginsonly ever described the browser's own built-in plugin/PDF-handling layer.
What navigator.plugins Actually Returns Now
Calling navigator.plugins still returns a PluginArray — not a real JavaScript array, but an array-like object with length, item(index), and namedItem(name). What's inside it, though, is no longer discovered from the operating system. Per current spec, the content is one of exactly two fixed outcomes:
- If the browser supports inline PDF viewing, the array contains five specific entries:
"PDF Viewer","Chrome PDF Viewer","Chromium PDF Viewer","Microsoft Edge PDF Viewer", and"WebKit built-in PDF". - If it doesn't, the array is empty.
if ("PDF Viewer" in navigator.plugins) {
// Browser supports inline viewing of PDF files.
}
That's it. There is no third state, no partial list, no way for a real, spec-compliant browser to report three plugins or a plugin named something else. MDN marks PluginArray as deprecated — a candidate for eventual removal — and its own properties are no longer enumerable in current browser versions, which closes off older tricks that iterated the array with a for...in loop.
navigator.mimeTypes went through the identical change. It returns a MimeTypeArray that, per spec, contains entries for application/pdf and text/pdf when inline PDF viewing is supported, and an empty list otherwise — hard-coded the same way, tied to the same one bit of underlying capability.
navigator.pdfViewerEnabled Is the Replacement — Not navigator.plugins
Because the plugin list collapsed to a single yes/no signal about PDF support, the platform added a property that says that directly: navigator.pdfViewerEnabled, a plain boolean. MDN is explicit about the intended migration on both the plugins and mimeTypes pages: use navigator.pdfViewerEnabled to determine whether inline viewing of PDF files is supported, and do not infer it from either of those older properties.
That instruction is the whole reason navigator.plugins is still worth writing about. Legitimate code that wants to know "can this browser show a PDF inline" has a direct, sanctioned answer now. Code that still branches on navigator.plugins.length or checks for "PDF Viewer" by name is either old, or it's doing something other than checking for PDF support — and that's exactly the kind of code a detector wants to characterize.
Why a Fixed List Is Still a Detection Signal
A value with only two legal states can't identify a visitor — but it can catch a browser environment that isn't behaving like the real thing it claims to be. That's the entire remaining use case, and it comes down to internal consistency rather than uniqueness:
An empty array where a full one is expected. A user agent string claiming a modern desktop Chrome build should, per spec, report the five-entry PDF viewer list — recent Chrome ships inline PDF viewing by default. An empty navigator.plugins on that claimed configuration is a mismatch worth flagging, and it's exactly the kind of gap that headless and automated browser environments have historically produced, sometimes because the automation harness disables the PDF viewer component entirely.
Entries that don't match the fixed set. Because the spec hard-codes the exact five plugin names, any plugin entry outside that list — a stray name, a different count, an entry with [object Object] where a string should be — indicates the property has been patched by a script rather than generated natively by the browser. Stealth-automation tooling that tries to fake a "realistic" plugin list is, ironically, the thing most likely to produce a list that doesn't match what a real, current browser actually emits. The patch usually leaves a second mark as well: replacing the property with a plain object or a JavaScript array fails checks that an untouched navigator.plugins passes — its prototype is still PluginArray, String(navigator.plugins) is still "[object PluginArray]", and its methods still stringify as native code.
Disagreement between navigator.plugins and navigator.pdfViewerEnabled. These two properties describe the same underlying capability from two different eras of the spec. In a genuine, unmodified browser they always agree: a non-empty plugin list implies pdfViewerEnabled === true, and vice versa. A page that queries both and finds them contradicting each other — pdfViewerEnabled true while navigator.plugins is empty, or the reverse — has caught a script overriding one property without touching the other, because spoofing code often patches the property it knows about and forgets the newer one exists.
None of these checks say who the visitor is. They say the environment is internally inconsistent, which is the signature automated and spoofed browser profiles leave behind far more often than genuine ones.
Not the Same Surface as Browser Extensions
It's easy to conflate "plugins" with "extensions" because both are historically add-ons to the browser, but they're detected — and matter — in completely different ways. navigator.plugins describes the browser's own built-in plugin and PDF-handling architecture, now reduced to that one hard-coded PDF signal. Browser extensions (uBlock Origin, password managers, ad blockers) are user-installed software, and a page can't enumerate them through this API at all — detecting installed extensions relies on separate techniques, like probing whether an extension's own web-accessible resources respond. If your interest is in what a page can learn about a user's installed extensions rather than the browser's built-in plugin flag, that's covered in our guide to browser extension privacy risks.
Entropy Reality Check
It's worth being blunt about how little navigator.plugins contributes on its own today. With only two legal states across every spec-compliant browser, it adds essentially no distinguishing information to a fingerprint — nowhere near what canvas rendering, installed fonts, or WebGL parameters contribute. Older write-ups on browser fingerprinting still describe navigator.plugins as a rich per-user signal because that was true before the hard-coding change; treating it that way today overstates its role. Its useful job now is narrower and different: not "who is this," but "does this environment's PDF-support signals agree with each other and with what it claims to be." That puts it in the same category as Permissions API state fingerprinting — another zero-prompt, zero-click read that's more useful as a consistency check than as a source of raw entropy. It's a poor substitute for signals that genuinely do still distinguish real devices from each other, like the camera and microphone inventory exposed by enumerateDevices(). For the fuller picture of which signals carry real distinguishing weight, see our browser fingerprinting guide.
Checking Your Own Browser
You can see exactly what your own browser reports for this property — the entries themselves, the count, and whether the PDF viewer is enabled — with BrowserInsight's plugin check tool. It runs the consistency side too: whether the plugin list agrees with navigator.mimeTypes, and whether the PluginArray object still looks native rather than patched, alongside the extension checks that pick up where navigator.plugins leaves off. For the full detection picture across canvas, WebGL, fonts, and every other signal a page can read, the fingerprint check puts this one property in context with everything else.
Frequently Asked Questions
Is it normal for navigator.plugins to be empty in a modern browser?
Yes. An empty array simply means the browser reports no inline PDF viewing support in its default configuration — that's a legal, spec-compliant state, not a sign of tampering by itself. It only becomes notable alongside other signals, like a user agent claiming a browser build that should support inline PDFs.
Can I still use navigator.plugins to check for PDF support?
You can, but you shouldn't — MDN explicitly recommends navigator.pdfViewerEnabled for that check instead. navigator.plugins and navigator.mimeTypes are both candidates for eventual removal.
Why would a bot's navigator.plugins list look wrong?
Because the real, spec-compliant list has exactly two possible shapes, any script that injects a "realistic-looking" custom plugin list to appear human is working against a fixed target: a detector already knows what a genuine list looks like, so anything else stands out immediately. A hand-built plugin list is more likely to reveal automation than hide it — especially if it doesn't match navigator.pdfViewerEnabled, or if the overridden object no longer looks like a native PluginArray.
Does navigator.plugins still matter for fingerprinting?
Barely, and not the way it used to. Its entropy contribution on its own is close to zero. Its remaining value is as a consistency check against navigator.pdfViewerEnabled and the browser's claimed identity, not as a way to tell two visitors apart.


