Puppeteer Stealth: Reliable JavaScript Rendering for Scrapers

Nathan Reynolds

Bypass Methods

Puppeteer is a Node.js library for driving Chrome or Chromium programmatically. It's a solid choice when you need to collect public data from pages that only render properly with JavaScript, run automated QA on your own web apps, or reproduce what a real visitor actually sees. Plain HTTP clients can't do any of that reliably, which is why a full browser matters.

The catch: a default headless browser doesn't look like a browser a person would use. It exposes automation-specific properties, ships with a giveaway user agent, and misses a handful of features regular Chrome includes. Many websites use those inconsistencies to decide whether to serve full content, a stripped-down page, or a challenge. The Puppeteer Stealth plugin closes those gaps so a legitimate scraper renders the same page a normal user would — which is usually all you need for accurate, respectful data collection.

What Puppeteer Extra and the Stealth Plugin Actually Do

Puppeteer Extra is a thin wrapper around standard Puppeteer that adds a plugin system. You keep the familiar Puppeteer API and layer functionality on top. There are several plugins — for example an adblocker plugin that cuts unnecessary requests, which is genuinely useful when you're on proxies billed by the gigabyte.

The best known is Stealth. Rather than doing anything sneaky, it patches the differences between a bare headless instance and a normal desktop Chrome install so the browser presents a consistent, believable environment. That consistency is important because inconsistent fingerprints — a desktop user agent paired with mobile-only APIs, say — are exactly what trip up rendering and cause pages to serve degraded content.

Keep expectations realistic. Stealth isn't a universal key, and it isn't meant to defeat security systems. Some sites run bespoke logic that will still flag automated traffic, and you should always respect a site's robots.txt, terms of service, and rate limits. Treat Stealth as one part of a well-behaved scraper, alongside quality proxies and sensible request pacing.

How Stealth Normalizes the Browser

Under the hood, Stealth applies a series of small corrections so the Puppeteer-controlled browser matches what regular Chrome reports:

  • Restoring standard browser features: Headless Chromium can differ from the desktop build in how it handles things like fonts, media codecs, and permission prompts. Stealth aligns these so feature detection behaves normally.

  • User agent consistency: The default headless user agent is an obvious mismatch. Stealth sets a current, ordinary user agent and makes sure related values (like the platform and client hints) line up with it.

  • The navigator.webdriver property: Automation exposes this flag by default. Real user browsers report it differently, so Stealth normalizes it to avoid a contradictory environment.

  • Plugin and fingerprint alignment: A typical desktop browser reports plugins, WebGL vendor strings, and canvas output in predictable ways. Stealth fills in consistent values so surfaces like WebGL and canvas don't stand out as blank or contradictory. If you want to go deeper on how these signals work, our write-up on canvas, WebGL and AudioContext fingerprinting is a good primer.

The practical result: pages render the way a human visitor would see them, so your extracted data is accurate. If you're comparing tooling, our guide to scraping with undetected-chromedriver covers the same idea in the Python ecosystem.

Setting Up Puppeteer Stealth

You'll need Node.js installed and any editor set up for JavaScript. From your project directory, install the core packages:

npm

Then add the Stealth plugin:

npm

Now wire it into a script. Note that you import and launch from puppeteer-extra, not from base puppeteer:

// Import puppeteer-extra
const puppeteer = require('puppeteer-extra');

// Import the stealth plugin
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

// Tell puppeteer to use the stealth plugin
puppeteer.use(StealthPlugin());

// Standard Puppeteer launch code, but using the 'puppeteer' variable configured with Stealth
puppeteer.launch({ headless: true })
  .then(async browser => {
    console.log('Running browser in stealth mode..');
    const page = await browser.newPage();

    // Let's navigate to Evomi's fingerprint checker to see what it detects
    await page.goto('https://check.evomi.com/', { waitUntil: 'domcontentloaded' });

    await page.screenshot({ path: 'stealth-check.png', fullPage: true }); // Optional: take a screenshot

    console.log('Page loaded. Closing browser.');
    await browser.close();
  });

This initializes Puppeteer Extra, enables Stealth, launches a headless browser, opens Evomi's free fingerprint checker so you can see exactly what the environment reports, and closes the browser. The important detail is that the puppeteer object here carries the Stealth patches.

Adding Proxies for Public Data Collection

For most real scraping work you'll route traffic through proxies — both to spread requests across IPs responsibly and to view public content from the geography you're researching. The quickest way is a launch argument. Here's an example pointing at an Evomi residential endpoint:

puppeteer.launch({
  headless: true,
  args: [
    '--proxy-server=http://rp.evomi.com:1000' // Replace with your specific proxy address/port
    // You might need authentication depending on the proxy setup:
    // '--proxy-auth=username:password' // Generally handled via proxy headers or specific plugins
  ]
}).then(async browser => {
  // ... your scraping logic
  // Consider using Evomi's free trial to test residential proxies!
  const page = await browser.newPage();
  // For authenticated proxies, you often need to handle it like this:
  // await page.authenticate({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' });
  await page.goto('https://geo.evomi.com/'); // Check perceived location via proxy
  console.log('Current IP Info Page Loaded via Proxy');
  await page.screenshot({ path: 'proxy-check.png' });
  await browser.close();
});

Swap http://rp.evomi.com:1000 for your own endpoint and port. For username/password proxies, call page.authenticate() after creating the page, as shown in the commented lines. To confirm the exit IP and location are what you expect, load Evomi's IP geolocation tool. If you're managing many targets, pairing Stealth with a rotating proxy setup keeps request volume per IP reasonable and polite. Evomi's residential, mobile, and datacenter plans all come with a free trial if you want to test before committing.

When Stealth Alone Isn't Enough

If a particular site still serves incomplete pages or challenges despite Stealth, a different tool may fit better:

  • Playwright: Maintained by Microsoft, it supports Node.js, Python, Java, and .NET, with a modern API and strong auto-waiting. It's a natural alternative when you want cross-language support.

  • Selenium: The long-standing standard for browser automation, with mature bindings across languages and community projects like undetected-chromedriver for cleaner Python environments.

  • Cypress: Primarily a front-end testing framework, but its different architecture can suit some in-browser automation and QA scenarios.

No framework guarantees a page will always render or that you'll never see a CAPTCHA — the goal is fewer interruptions and cleaner data, not zero friction. If you'd rather skip infrastructure entirely, Evomi's Scraping Browser is a managed cloud Chromium you connect to over wss://browser.evomi.com, compatible with both Puppeteer and Playwright, with proxies handled for you. Combining a capable automation tool with ethically sourced proxies remains the most dependable foundation for collecting public data at scale — responsibly and within each site's terms.

Puppeteer is a Node.js library for driving Chrome or Chromium programmatically. It's a solid choice when you need to collect public data from pages that only render properly with JavaScript, run automated QA on your own web apps, or reproduce what a real visitor actually sees. Plain HTTP clients can't do any of that reliably, which is why a full browser matters.

The catch: a default headless browser doesn't look like a browser a person would use. It exposes automation-specific properties, ships with a giveaway user agent, and misses a handful of features regular Chrome includes. Many websites use those inconsistencies to decide whether to serve full content, a stripped-down page, or a challenge. The Puppeteer Stealth plugin closes those gaps so a legitimate scraper renders the same page a normal user would — which is usually all you need for accurate, respectful data collection.

What Puppeteer Extra and the Stealth Plugin Actually Do

Puppeteer Extra is a thin wrapper around standard Puppeteer that adds a plugin system. You keep the familiar Puppeteer API and layer functionality on top. There are several plugins — for example an adblocker plugin that cuts unnecessary requests, which is genuinely useful when you're on proxies billed by the gigabyte.

The best known is Stealth. Rather than doing anything sneaky, it patches the differences between a bare headless instance and a normal desktop Chrome install so the browser presents a consistent, believable environment. That consistency is important because inconsistent fingerprints — a desktop user agent paired with mobile-only APIs, say — are exactly what trip up rendering and cause pages to serve degraded content.

Keep expectations realistic. Stealth isn't a universal key, and it isn't meant to defeat security systems. Some sites run bespoke logic that will still flag automated traffic, and you should always respect a site's robots.txt, terms of service, and rate limits. Treat Stealth as one part of a well-behaved scraper, alongside quality proxies and sensible request pacing.

How Stealth Normalizes the Browser

Under the hood, Stealth applies a series of small corrections so the Puppeteer-controlled browser matches what regular Chrome reports:

  • Restoring standard browser features: Headless Chromium can differ from the desktop build in how it handles things like fonts, media codecs, and permission prompts. Stealth aligns these so feature detection behaves normally.

  • User agent consistency: The default headless user agent is an obvious mismatch. Stealth sets a current, ordinary user agent and makes sure related values (like the platform and client hints) line up with it.

  • The navigator.webdriver property: Automation exposes this flag by default. Real user browsers report it differently, so Stealth normalizes it to avoid a contradictory environment.

  • Plugin and fingerprint alignment: A typical desktop browser reports plugins, WebGL vendor strings, and canvas output in predictable ways. Stealth fills in consistent values so surfaces like WebGL and canvas don't stand out as blank or contradictory. If you want to go deeper on how these signals work, our write-up on canvas, WebGL and AudioContext fingerprinting is a good primer.

The practical result: pages render the way a human visitor would see them, so your extracted data is accurate. If you're comparing tooling, our guide to scraping with undetected-chromedriver covers the same idea in the Python ecosystem.

Setting Up Puppeteer Stealth

You'll need Node.js installed and any editor set up for JavaScript. From your project directory, install the core packages:

npm

Then add the Stealth plugin:

npm

Now wire it into a script. Note that you import and launch from puppeteer-extra, not from base puppeteer:

// Import puppeteer-extra
const puppeteer = require('puppeteer-extra');

// Import the stealth plugin
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

// Tell puppeteer to use the stealth plugin
puppeteer.use(StealthPlugin());

// Standard Puppeteer launch code, but using the 'puppeteer' variable configured with Stealth
puppeteer.launch({ headless: true })
  .then(async browser => {
    console.log('Running browser in stealth mode..');
    const page = await browser.newPage();

    // Let's navigate to Evomi's fingerprint checker to see what it detects
    await page.goto('https://check.evomi.com/', { waitUntil: 'domcontentloaded' });

    await page.screenshot({ path: 'stealth-check.png', fullPage: true }); // Optional: take a screenshot

    console.log('Page loaded. Closing browser.');
    await browser.close();
  });

This initializes Puppeteer Extra, enables Stealth, launches a headless browser, opens Evomi's free fingerprint checker so you can see exactly what the environment reports, and closes the browser. The important detail is that the puppeteer object here carries the Stealth patches.

Adding Proxies for Public Data Collection

For most real scraping work you'll route traffic through proxies — both to spread requests across IPs responsibly and to view public content from the geography you're researching. The quickest way is a launch argument. Here's an example pointing at an Evomi residential endpoint:

puppeteer.launch({
  headless: true,
  args: [
    '--proxy-server=http://rp.evomi.com:1000' // Replace with your specific proxy address/port
    // You might need authentication depending on the proxy setup:
    // '--proxy-auth=username:password' // Generally handled via proxy headers or specific plugins
  ]
}).then(async browser => {
  // ... your scraping logic
  // Consider using Evomi's free trial to test residential proxies!
  const page = await browser.newPage();
  // For authenticated proxies, you often need to handle it like this:
  // await page.authenticate({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' });
  await page.goto('https://geo.evomi.com/'); // Check perceived location via proxy
  console.log('Current IP Info Page Loaded via Proxy');
  await page.screenshot({ path: 'proxy-check.png' });
  await browser.close();
});

Swap http://rp.evomi.com:1000 for your own endpoint and port. For username/password proxies, call page.authenticate() after creating the page, as shown in the commented lines. To confirm the exit IP and location are what you expect, load Evomi's IP geolocation tool. If you're managing many targets, pairing Stealth with a rotating proxy setup keeps request volume per IP reasonable and polite. Evomi's residential, mobile, and datacenter plans all come with a free trial if you want to test before committing.

When Stealth Alone Isn't Enough

If a particular site still serves incomplete pages or challenges despite Stealth, a different tool may fit better:

  • Playwright: Maintained by Microsoft, it supports Node.js, Python, Java, and .NET, with a modern API and strong auto-waiting. It's a natural alternative when you want cross-language support.

  • Selenium: The long-standing standard for browser automation, with mature bindings across languages and community projects like undetected-chromedriver for cleaner Python environments.

  • Cypress: Primarily a front-end testing framework, but its different architecture can suit some in-browser automation and QA scenarios.

No framework guarantees a page will always render or that you'll never see a CAPTCHA — the goal is fewer interruptions and cleaner data, not zero friction. If you'd rather skip infrastructure entirely, Evomi's Scraping Browser is a managed cloud Chromium you connect to over wss://browser.evomi.com, compatible with both Puppeteer and Playwright, with proxies handled for you. Combining a capable automation tool with ethically sourced proxies remains the most dependable foundation for collecting public data at scale — responsibly and within each site's terms.

Author

Nathan Reynolds

Web Scraping & Automation Specialist

About Author

Nathan specializes in web scraping techniques, automation tools, and data-driven decision-making. He helps businesses extract valuable insights from the web using ethical and efficient scraping methods powered by advanced proxies. His expertise covers overcoming anti-bot mechanisms, optimizing proxy rotation, and ensuring compliance with data privacy regulations.

Like this article? Share it.
You asked, we answer - Users questions:
Is Puppeteer Stealth legal to use?+
Why do I need to import from puppeteer-extra instead of puppeteer?+
Does Stealth guarantee I'll never be blocked?+
Should I use residential or datacenter proxies with Puppeteer?+
How can I verify what my browser environment reports?+
Can I avoid managing Puppeteer infrastructure myself?+

In This Article