Puppeteer Stealth: Evade Bot Detection in 2026
Puppeteer is Google’s official Node.js library for controlling Chrome. Out of the box, it’s easily detected by anti-bot systems because headless Chrome leaks dozens of automation signals. The puppeteer-extra-plugin-stealth plugin patches these leaks, making your automated browser appear nearly identical to a real user’s browser.
This guide covers installation, configuration, and advanced techniques for evading bot detection with Puppeteer Stealth.
Why Standard Puppeteer Gets Detected
When you launch Puppeteer without stealth:
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://bot.sannysoft.com');Anti-bot systems detect it through:
navigator.webdriveristrue- Missing Chrome runtime properties (
window.chrome) - Headless-specific properties in
navigator - Empty
navigator.pluginsarray - Detectable WebGL renderer strings
- Missing or incorrect
navigator.permissions - Chrome DevTools Protocol (CDP) artifacts
Installation
npm install puppeteer puppeteer-extra puppeteer-extra-plugin-stealthBasic Setup
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
// Add stealth plugin
puppeteer.use(StealthPlugin());
(async () => {
const browser = await puppeteer.launch({
headless: 'new', // Use new headless mode
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--window-size=1920,1080',
]
});
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
await page.goto('https://bot.sannysoft.com', {
waitUntil: 'networkidle2'
});
// Take screenshot to verify stealth
await page.screenshot({ path: 'stealth-test.png', fullPage: true });
await browser.close();
})();What Stealth Plugin Patches
The stealth plugin applies multiple evasion modules:
| Evasion | What It Does |
|---|---|
chrome.app | Adds missing chrome.app properties |
chrome.csi | Adds chrome.csi() function |
chrome.loadTimes | Adds chrome.loadTimes() function |
chrome.runtime | Fakes chrome.runtime to prevent undefined check |
defaultArgs | Removes --enable-automation from Chrome args |
iframe.contentWindow | Patches iframe detection vectors |
media.codecs | Provides correct media codec support |
navigator.hardwareConcurrency | Sets realistic CPU core count |
navigator.languages | Ensures navigator.languages is populated |
navigator.permissions | Fixes permissions API responses |
navigator.plugins | Adds realistic plugin entries |
navigator.webdriver | Sets navigator.webdriver to undefined |
sourceurl | Hides sourceURL from stack traces |
user-agent-override | Removes “HeadlessChrome” from UA |
webgl.vendor | Spoofs WebGL vendor and renderer strings |
window.outerdimensions | Sets correct outerWidth/outerHeight |
Configuring Individual Evasions
You can enable or disable specific evasions:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const stealth = StealthPlugin();
// Disable specific evasions if they cause issues
stealth.enabledEvasions.delete('chrome.runtime');
stealth.enabledEvasions.delete('iframe.contentWindow');
puppeteer.use(stealth);Adding Proxy Support
Basic Proxy
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--proxy-server=http://proxy-host:7777',
'--no-sandbox',
]
});Authenticated Proxy
const browser = await puppeteer.launch({
headless: 'new',
args: ['--proxy-server=http://proxy-host:7777']
});
const page = await browser.newPage();
// Authenticate with proxy
await page.authenticate({
username: 'proxy_user',
password: 'proxy_pass'
});
await page.goto('https://target-site.com');Rotating Residential Proxies
For best results against anti-bot systems, use residential proxies:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function scrapeWithProxy(url, proxyUrl) {
const browser = await puppeteer.launch({
headless: 'new',
args: [`--proxy-server=${proxyUrl}`]
});
const page = await browser.newPage();
await page.authenticate({
username: 'user',
password: 'pass'
});
await page.goto(url, { waitUntil: 'networkidle2' });
const content = await page.content();
await browser.close();
return content;
}
// Use with residential proxy
const html = await scrapeWithProxy(
'https://cloudflare-protected.com',
'http://residential-gateway.example.com:7777'
);Bypassing Cloudflare with Puppeteer Stealth
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function bypassCloudflare(url) {
const browser = await puppeteer.launch({
headless: false, // Headed mode for maximum stealth
args: ['--window-size=1920,1080']
});
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
await page.goto(url, { waitUntil: 'domcontentloaded' });
// Wait for Cloudflare challenge to complete
await page.waitForFunction(
() => !document.querySelector('#challenge-running'),
{ timeout: 30000 }
).catch(() => console.log('No challenge detected'));
// Additional wait for page to fully load
await new Promise(resolve => setTimeout(resolve, 5000));
// Check if we passed
const title = await page.title();
console.log(`Page title: ${title}`);
// Extract cookies for session transfer
const cookies = await page.cookies();
const cfClearance = cookies.find(c => c.name === 'cf_clearance');
if (cfClearance) {
console.log(`cf_clearance obtained: ${cfClearance.value.slice(0, 30)}...`);
}
const html = await page.content();
await browser.close();
return { html, cookies };
}
bypassCloudflare('https://cloudflare-site.com');Human-Like Behavior Simulation
Stealth patches alone aren’t enough for advanced bot detection. Add behavioral signals:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function humanLikeScrape(url) {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
await page.goto(url, { waitUntil: 'networkidle2' });
// Random mouse movements
for (let i = 0; i < 5; i++) {
const x = Math.floor(Math.random() * 1200) + 100;
const y = Math.floor(Math.random() * 700) + 100;
await page.mouse.move(x, y, { steps: 10 });
await new Promise(r => setTimeout(r, Math.random() * 500 + 200));
}
// Realistic scrolling
await page.evaluate(async () => {
const scrollAmount = Math.floor(Math.random() * 500) + 200;
window.scrollBy({ top: scrollAmount, behavior: 'smooth' });
await new Promise(r => setTimeout(r, 1000));
});
// Random delay before action
await new Promise(r => setTimeout(r, Math.random() * 2000 + 1000));
const content = await page.content();
await browser.close();
return content;
}Puppeteer Stealth vs Alternatives
| Feature | Puppeteer Stealth | Playwright Stealth | Undetected ChromeDriver |
|---|---|---|---|
| Language | JavaScript | Python/JS | Python |
| Evasion quality | Good | Good | Excellent |
| Plugin ecosystem | Rich | Growing | Limited |
| Headless support | Yes | Yes | Yes |
| Maintained | Active | Community | Active |
| Speed | Fast | Fast | Moderate |
| API design | Modern | Modern | Selenium-based |
For Python-based alternatives, see our Playwright Stealth guide and Undetected ChromeDriver tutorial.
Scaling with Puppeteer Cluster
For production scraping, use puppeteer-cluster to manage multiple browser instances:
const { Cluster } = require('puppeteer-cluster');
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
(async () => {
const cluster = await Cluster.launch({
puppeteer,
concurrency: Cluster.CONCURRENCY_CONTEXT,
maxConcurrency: 5,
puppeteerOptions: {
headless: 'new',
args: ['--no-sandbox']
}
});
await cluster.task(async ({ page, data: url }) => {
await page.goto(url, { waitUntil: 'networkidle2' });
const title = await page.title();
console.log(`${url}: ${title}`);
});
const urls = [
'https://site1.com',
'https://site2.com',
'https://site3.com',
];
for (const url of urls) {
cluster.queue(url);
}
await cluster.idle();
await cluster.close();
})();Troubleshooting
“Navigation timeout” on Cloudflare
Increase the timeout and use headed mode:
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
page.setDefaultNavigationTimeout(60000);
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });Still detected on some sites
Some anti-bot systems (DataDome, Kasada) go beyond what stealth patches can fix. For those, you need:
- Residential proxies for clean IP reputation
- Human behavior simulation (mouse, scroll, clicks)
- Proper browser fingerprinting management
High memory usage
Each browser instance uses 200-500MB RAM. Manage this by:
- Using
Cluster.CONCURRENCY_PAGEinstead ofCONCURRENCY_BROWSER - Closing pages promptly
- Limiting concurrent instances
FAQ
Is Puppeteer Stealth better than Playwright for scraping?
Neither is objectively better. Puppeteer Stealth has a more mature evasion plugin, but Playwright offers multi-browser support (Chrome, Firefox, WebKit) and a cleaner API. For Cloudflare bypass, both work well with residential proxies. Choose based on your language preference (JavaScript vs Python).
Does Puppeteer Stealth work in headless mode?
Yes, especially with Chrome’s headless: 'new' mode (available since Chrome 109). The new headless mode is much harder to detect than the old mode. However, headed mode still has the highest bypass rate for the strictest anti-bot systems.
Can I use Puppeteer Stealth with TypeScript?
Yes. Install the types package: npm install @types/puppeteer-extra @types/puppeteer-extra-plugin-stealth. The plugin works identically in TypeScript.
How often does the stealth plugin need updates?
Anti-bot companies regularly update their detection methods. The stealth plugin’s maintainers push updates in response, but there can be lag periods. Check the GitHub repository for recent commits.
Conclusion
Puppeteer Stealth is the standard solution for JavaScript developers who need to evade bot detection. It patches the most common detection vectors out of the box and provides a clean plugin architecture for customization. For maximum effectiveness, combine it with residential proxies, human-like behavior simulation, and proper IP rotation.
Useful Resources
- puppeteer-extra-plugin-stealth GitHub
- Puppeteer Documentation
- How Websites Detect Bots
- Bypass Cloudflare Protection
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research
Related Reading
- 403 Forbidden in Web Scraping: How to Fix It
- Best CAPTCHA Solving Services in 2026: Complete Comparison
- Anti-Phishing with Proxies: How Security Teams Use Mobile IPs
- Brand Protection with Proxies: Detect Counterfeit Sellers & Trademark Violations
- How Cybersecurity Teams Use Proxies for Threat Intelligence
- Using Mobile Proxies for Dark Web Monitoring and Research