The stealth plugin is the most recommended answer to being blocked in Node. It is also the most misunderstood one, because it does nothing at all about where your traffic comes from. We read the repository on 20 September 2026, and there is a second plugin in it that almost nobody links.
A date first, because the recommendations rarely carry one. The packages were last published on npm in March 2023. The repository was last pushed to in July 2024. It still works. Nothing in it has moved in a long time.
What the stealth plugin covers
The plugin is a bundle of seventeen small patches. Each one fixes a way that a headless Chrome differs from a normal one:
The names of the evasions are the evidence. They are chrome.app, chrome.csi, chrome.loadTimes, chrome.runtime, defaultArgs, iframe.contentWindow, media.codecs, navigator.hardwareConcurrency, navigator.languages, navigator.permissions, navigator.plugins, navigator.vendor, navigator.webdriver, sourceurl, user-agent-override, webgl.vendor and window.outerdimensions.
There is no WebRTC evasion. There is no timezone evasion. There is no geolocation evasion. The plugin makes a browser look less automated, which is a real and useful job, and it is not the job of looking like it comes from somewhere.
Two evasions that argue with your proxy
The fourth row of that chart needs a footnote, because the plugin does set your language and its default can work against you.
Two evasions announce it. navigator.languages falls back to ['en-US', 'en']. The user agent evasion sets the Accept-Language header and the same property, with a documented default of en-US,en. Both run on every page.
So route through Germany with default settings, and every request says en-US while the address says Germany. That is a contradiction a site can read without doing any work at all.
The fix is documented, in a comment inside one evasion. Take that evasion out of the bundle and add it back with a locale:
const stealth = StealthPlugin()
stealth.enabledEvasions.delete('user-agent-override')
puppeteer.use(stealth)
const UserAgentOverride = require('puppeteer-extra-plugin-stealth/evasions/user-agent-override')
puppeteer.use(UserAgentOverride({ locale: 'de-DE,de' }))
One more warning from the same file, worth repeating: calling page.setUserAgent() yourself resets the language and platform values this evasion set.
The plugin nobody links
The same repository ships @extra/proxy-router, a plugin for both puppeteer-extra and playwright-extra whose whole job is proxies. Not one of the six rival articles we read for this page mentions it.
What it offers, in their words: it handles proxy authentication, takes several proxies at once, routes by host or domain, changes proxies after the browser has launched, and collects traffic stats per proxy or per host.
It works the way three earlier tools in this series do. It starts a local proxy server, points the browser at that, and relays onward to the real address while answering the upstream login. The library underneath is the same one Crawlee uses. That pattern is now the standard answer to Chrome refusing a login in a launch flag.
Routing by host
Configure the addresses as a dictionary of names, then write one function. It receives a host and returns a decision:
| Return value | What happens |
|---|---|
| a proxy name | that address is used for this host |
DEFAULT, null or nothing | the default address is used |
DIRECT | no proxy, the request goes out normally |
ABORT | the request is cancelled |
One caveat from their own documentation: the browser usually makes a single proxy connection per host, so the decision is per host rather than per request.
Where the bytes went
The interesting part is the accounting. The plugin reports bytes per proxy and per host, and their own documented example says more about browser traffic than any argument we could write:
| Lane | Bytes it carried |
|---|---|
| The datacenter line, carrying map tiles | 441,734 |
| The default line | 125,823 |
| Direct connections | 100,457 |
| The residential line | 4,764 |
| Blocked hosts | 0 |
That is a run where the expensive address carried under five kilobytes and a pile of map tiles cost nearly half a megabyte somewhere cheap. On a per gigabyte line that difference is the entire bill.
Which proxy type fits it?
Residential, for the sites that answer a hosting address differently. Our own test is the size of that effect: 4 of 13 answers changed between a server address and a residential one, with plain requests and no browser involved.
The routing plugin makes the cost argument easy here. Put the residential line on the hosts that matter, send fonts, tiles and advertising scripts direct or to a cheaper address, and block what you do not need at all. The byte table above is that idea already measured by the author.
An allowed address is worth considering on a server, because then no password sits in a plugin configuration: on a Residential Premium plan your machine's address can be allowed, up to 150 per plan. A sticky port suits a run that works through one site, with a retry in your code because a sticky address can change early when its device leaves the network. The residential proxies page lists the plans, and the plan API manages allowed addresses from code.
What breaks
- Stealth is on and you are still refused. The plugin never changed your address. Check what the site sees.
- Your proxy country and your browser disagree. The language defaults to en-US in two places. Set the locale.
- You set a user agent yourself and the locale reverted. That call resets what the evasion did.
- Only one address is in use. The launch option takes one. Several addresses need the routing plugin.
- The traffic bill is larger than the page count. Route the heavy hosts somewhere else, or block them.
What this page does not cover
We read the repository and the registry as text and did not install or run anything, so we did not test the stealth plugin against a detector, did not start the routing plugin, and did not reproduce its byte table. Those numbers are the author's documented example, not our measurement. We read the evasion listing, two evasions, the routing plugin's documentation and its dependencies closely, and we did not read the router's implementation. We did not establish whether the stealth plugin still works against current detectors, which is the question its age raises. We will look again by 20 December 2026.
Where to go from here
Proxies for Crawlee covers the same local forwarder pattern in a crawler that also rotates addresses by tier. Proxies for Camoufox covers a browser that looks up the exit address and aligns the timezone and language with it, which is the job this plugin leaves to you. Proxies for PinchTab covers a tool that treats the proxy as validated configuration rather than a launch flag.
Sources
- The stealth plugin's evasion directory, the language evasion and its default, the user agent evasion with its
Accept-Languagedefault and the documented way to pass it options. berstend/puppeteer-extra, head of master, read 20 September 2026. The repository has no releases and no tags. - The routing plugin: its feature list, how it works, the routing function's return values, the traffic statistics and the example output, and its dependency on the local forwarder library. packages/plugin-proxy-router, published as @extra/proxy-router 3.1.6.
- Publish dates for puppeteer-extra 3.3.6, puppeteer-extra-plugin-stealth 2.11.2 and @extra/proxy-router 3.1.6, all 1 March 2023. npm registry, read 20 September 2026.
- Our paired address test of 19 September 2026: 16 URLs, plain requests, two runs from our server and two through a residential line of our house plan. Raw output is kept in the research folder of our OpenClaw page.
- Plans, allowed addresses and per gigabyte pricing. HProxy documentation, hproxy.com/docs, 20 September 2026.


