Puppeteer Stealth: Evade Bot Detection in 2026

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.webdriver is true
  • Missing Chrome runtime properties (window.chrome)
  • Headless-specific properties in navigator
  • Empty navigator.plugins array
  • Detectable WebGL renderer strings
  • Missing or incorrect navigator.permissions
  • Chrome DevTools Protocol (CDP) artifacts

Installation

npm install puppeteer puppeteer-extra puppeteer-extra-plugin-stealth

Basic 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:

EvasionWhat It Does
chrome.appAdds missing chrome.app properties
chrome.csiAdds chrome.csi() function
chrome.loadTimesAdds chrome.loadTimes() function
chrome.runtimeFakes chrome.runtime to prevent undefined check
defaultArgsRemoves --enable-automation from Chrome args
iframe.contentWindowPatches iframe detection vectors
media.codecsProvides correct media codec support
navigator.hardwareConcurrencySets realistic CPU core count
navigator.languagesEnsures navigator.languages is populated
navigator.permissionsFixes permissions API responses
navigator.pluginsAdds realistic plugin entries
navigator.webdriverSets navigator.webdriver to undefined
sourceurlHides sourceURL from stack traces
user-agent-overrideRemoves “HeadlessChrome” from UA
webgl.vendorSpoofs WebGL vendor and renderer strings
window.outerdimensionsSets 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

FeaturePuppeteer StealthPlaywright StealthUndetected ChromeDriver
LanguageJavaScriptPython/JSPython
Evasion qualityGoodGoodExcellent
Plugin ecosystemRichGrowingLimited
Headless supportYesYesYes
MaintainedActiveCommunityActive
SpeedFastFastModerate
API designModernModernSelenium-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:

  1. Residential proxies for clean IP reputation
  2. Human behavior simulation (mouse, scroll, clicks)
  3. Proper browser fingerprinting management

High memory usage

Each browser instance uses 200-500MB RAM. Manage this by:

  • Using Cluster.CONCURRENCY_PAGE instead of CONCURRENCY_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


Related Reading

Scroll to Top