Puppeteer Stealth Mode in 2026

How to bypass Cloudflare, DataDome, and other anti-bot systems using puppeteer-extra-plugin-stealth, fingerprint patching, and proxy rotation — plus a managed API option when you need guaranteed delivery.

PuppeteerPlaywrightStealth CloudflareDataDomeApril 2026

How Bot Detection Works in 2026

Modern anti-bot systems don't just check your User-Agent. They build a fingerprint from dozens of signals and cross-reference them. If any combination doesn't match a "real browser profile," you get a 403, a CAPTCHA, or a silent redirect to a honeypot page.

puppeteer-extra-plugin-stealth

The most widely used stealth solution for Puppeteer. It patches 11+ evasions in a single plugin, covering most of the detection vectors above.

Install

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

Basic usage

import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';

puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({
  headless: 'new',
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
await page.goto('https://bot.sannysoft.com');   // fingerprint test page
await page.screenshot({ path: 'stealth-test.png', fullPage: true });
await browser.close();
Visit bot.sannysoft.com or fingerprintjs BotD to test your stealth setup. A clean result shows all green checkmarks.

Individual evasion control

import StealthPlugin from 'puppeteer-extra-plugin-stealth';

// Enable only the evasions you need
const stealth = StealthPlugin();
stealth.enabledEvasions.clear();
stealth.enabledEvasions.add('chrome.app');
stealth.enabledEvasions.add('chrome.csi');
stealth.enabledEvasions.add('chrome.loadTimes');
stealth.enabledEvasions.add('chrome.runtime');
stealth.enabledEvasions.add('navigator.languages');
stealth.enabledEvasions.add('navigator.permissions');
stealth.enabledEvasions.add('navigator.plugins');
stealth.enabledEvasions.add('navigator.webdriver');    // most critical
stealth.enabledEvasions.add('sourceurl');
stealth.enabledEvasions.add('user-agent-override');
stealth.enabledEvasions.add('webgl.vendor');

puppeteer.use(stealth);

Manual Fingerprint Patches

For sites that have gotten wise to the stealth plugin, you can patch at the CDP (Chrome DevTools Protocol) level directly:

const page = await browser.newPage();

// Remove webdriver flag
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
});

// Inject realistic plugin list
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'plugins', {
    get: () => [
      { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
      { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
      { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
    ],
  });
});

// Realistic language list
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
});

// Spoof WebGL
await page.evaluateOnNewDocument(() => {
  const getParameter = WebGLRenderingContext.prototype.getParameter;
  WebGLRenderingContext.prototype.getParameter = function(parameter) {
    if (parameter === 37445) return 'Intel Inc.';            // UNMASKED_VENDOR_WEBGL
    if (parameter === 37446) return 'Intel Iris OpenGL Engine'; // UNMASKED_RENDERER_WEBGL
    return getParameter.call(this, parameter);
  };
});

User-Agent & Headers

Your UA must match your browser version exactly. A mismatch between the reported UA and actual Chromium version is an instant red flag.

// Get actual Chromium version from the browser
const version = await browser.version();
// e.g. "HeadlessChrome/124.0.6367.207"

await page.setUserAgent(
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
  '(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
);

// Set realistic Accept-Language and other headers
await page.setExtraHTTPHeaders({
  'Accept-Language': 'en-US,en;q=0.9',
  'Accept-Encoding': 'gzip, deflate, br',
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
  'Upgrade-Insecure-Requests': '1',
});

Proxy Rotation

IP-based rate limiting and geoblocking are the most common blocks after fingerprinting. Rotating residential proxies are the most effective solution — datacenter IPs are flagged by most systems.

const proxies = [
  'http://user:pass@proxy1.provider.com:10000',
  'http://user:pass@proxy2.provider.com:10001',
  'http://user:pass@proxy3.provider.com:10002',
];

function pickProxy() {
  return proxies[Math.floor(Math.random() * proxies.length)];
}

async function stealthFetch(url) {
  const proxy = pickProxy();
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      '--no-sandbox',
      `--proxy-server=${proxy}`,
    ],
  });
  const page = await browser.newPage();

  // Set timezone to match proxy location
  await page.emulateTimezone('America/New_York');

  try {
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 20000 });
    return await page.content();
  } finally {
    await browser.close();
  }
}
Proxy auth gotcha: Puppeteer doesn't support proxy authentication via URL string in some setups. Use page.authenticate() as a fallback: await page.authenticate({ username: 'user', password: 'pass' })

Stealth with Playwright

Playwright doesn't have an official stealth plugin, but you can apply the same patches via addInitScript. There's also playwright-extra with stealth plugin support:

import { chromium } from 'playwright-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';

chromium.use(StealthPlugin());

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'playwright-stealth.png' });
await browser.close();

Or manually with addInitScript:

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
  userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
  locale: 'en-US',
  timezoneId: 'America/New_York',
  viewport: { width: 1280, height: 800 },
  deviceScaleFactor: 2,
});

await context.addInitScript(() => {
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
  Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3] }); // truthy length
});

const page = await context.newPage();
await page.goto('https://example.com');

Handling Cloudflare Challenges

Cloudflare's Bot Management (formerly "Under Attack Mode") uses JavaScript challenges that execute in the browser. Plain waitUntil: 'networkidle' won't work — you need to wait for the challenge to resolve.

async function bypassCloudflare(page, url) {
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });

  // Wait for CF challenge to complete (it reloads the page)
  try {
    await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 });
  } catch {
    // No redirect — page loaded directly (no CF challenge was triggered)
  }

  // Verify we're past the challenge
  const title = await page.title();
  if (title.includes('Just a moment')) {
    throw new Error('Cloudflare challenge not resolved — rotate proxy or add delay');
  }

  return page;
}
Cloudflare Turnstile (the CAPTCHA-like challenge) cannot be bypassed with browser automation alone in 2026. It requires either a CAPTCHA-solving service or using an API like SnapAPI that handles the challenge infrastructure.

Human Behavior Simulation

For the most aggressive bot detectors, you need to simulate human interaction patterns — random delays, mouse movement, realistic scroll behavior.

// Random delay between min and max ms
const delay = (min, max) =>
  new Promise(r => setTimeout(r, min + Math.random() * (max - min)));

// Simulate human-like mouse movement to an element
async function humanClick(page, selector) {
  const el = await page.$(selector);
  const box = await el.boundingBox();

  // Move to a random point within the element
  const x = box.x + box.width * (0.2 + Math.random() * 0.6);
  const y = box.y + box.height * (0.2 + Math.random() * 0.6);

  await page.mouse.move(x - 50, y - 30);          // approach from offset
  await delay(80, 200);
  await page.mouse.move(x, y, { steps: 8 });       // smooth movement
  await delay(50, 150);
  await page.mouse.click(x, y);
}

// Simulate human scroll
async function humanScroll(page) {
  const totalHeight = await page.evaluate(() => document.body.scrollHeight);
  let scrolled = 0;
  while (scrolled < totalHeight) {
    const step = 200 + Math.random() * 300;
    await page.evaluate(s => window.scrollBy(0, s), step);
    scrolled += step;
    await delay(100, 400);
  }
}

// Usage
await page.goto(url, { waitUntil: 'domcontentloaded' });
await delay(800, 2000);         // human read delay
await humanScroll(page);
await delay(500, 1500);
await page.screenshot({ path: 'stealth.png' });

SnapAPI: Managed Stealth — Zero Config

Maintaining stealth infrastructure is a full-time job — browser fingerprints evolve, anti-bot vendors update their detection weekly, and proxies get burned. SnapAPI handles all of it. Pass stealth: true and it handles Cloudflare, rotating proxies, and fingerprint management automatically.

import { SnapAPI } from 'snapapi-js';

const snap = new SnapAPI({ apiKey: process.env.SNAPAPI_KEY });

// Stealth screenshot — handles Cloudflare challenges automatically
const { data } = await snap.screenshot({
  url: 'https://heavily-protected-site.com',
  stealth: true,
  blockAds: true,
  blockCookieBanners: true,
  waitUntil: 'networkidle',
  fullPage: true,
});
// Returns PNG buffer — no proxy management, no fingerprint tuning needed
// Stealth scrape — full HTML after JS execution + bot bypass
const { data } = await snap.scrape({
  url: 'https://protected-site.com/listings',
  stealth: true,
  waitForSelector: '.product-list',
});
console.log(data.html); // full rendered HTML, past any challenges

Stealth Approach Comparison

ApproachCloudflareDataDomeSetup effortMaintenanceCost
puppeteer-extra-plugin-stealthPartialPartialLowMedium (updates)Free
Manual CDP patches + proxyGoodGoodHighHighProxy cost
playwright-extra + stealthPartialPartialLowMediumFree
SnapAPI (stealth: true)✅ Full✅ FullMinimalNone$79/mo (50K)
SnapAPI free tier includes stealth mode (200 req/month). No credit card required. Get your API key →

Quick Stealth Checklist