How to Use Puppeteer Stealth in 5 Steps (2025)

Puppeteer Stealth is a plugin designed to disguise Puppeteer’s browser footprint so your automation doesn’t scream “I’m a bot!” to websites. It helps cover up typical signs of automation, like navigator.webdriver or the telltale HeadlessChrome string, making it easier for your scrapers to sneak past detection.

Getting blocked while using Puppeteer? You're definitely not the only one. Websites these days are quick to pick up on automation—especially when you're using the default, out-of-the-box Puppeteer setup. Clues like webdriver or even just the wrong user agent can tip them off instantly.

Enter Puppeteer Stealth. This handy plugin is part of the puppeteer-extra toolkit, and it works behind the scenes to mask the signs that usually give your bot away.

In this guide, I’ll take you step by step through getting Puppeteer Stealth up and running. We’ll start with the basics, but also dive into more advanced tricks you can try when stealth mode alone doesn’t cut it.

Why You Can Trust This Guide

The Problem: Standard Puppeteer sticks out like a sore thumb and often gets caught by anti-bot filters, which means blocked requests and scraping failures.

The Fix: Puppeteer Stealth takes care of the most common red flags bots usually trigger, making your automation blend in more naturally with real users.

The Proof: This plugin is tried, tested, and used by a massive community—with more than 450k weekly downloads on npm. Plus, it consistently passes public bot detection tests.

Step 1: Install Puppeteer Extra and the Stealth Plugin

First things first, you need to get the right packages installed. Puppeteer Stealth works with puppeteer-extra, not just regular Puppeteer.

Run this in your terminal:

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

Or with Yarn:

yarn add puppeteer puppeteer-extra puppeteer-extra-plugin-stealth

Quick tip: Already have Puppeteer? You’ll still need puppeteer-extra—it’s a separate package that enables plugin support.

Installation Pitfalls to Watch Out For

  • Wrong imports: Make sure you're importing from 'puppeteer-extra', not 'puppeteer'.
  • Node.js issues: Aim for Node.js v18 or newer to avoid compatibility headaches.
  • Dependency hiccups: Stealth loads modular evasions as needed, so don’t worry if you don’t see everything up front.

Step 2: Configure Basic Stealth Mode

Let’s get your stealth setup off the ground. This is the simplest setup to get started:

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

// Add stealth plugin with default settings
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

// Launch browser with stealth enabled
(async () => {
  const browser = await puppeteer.launch({ 
    headless: true // Can use 'new' for newer headless mode
  });
  
  const page = await browser.newPage();
  await page.goto('https://example.com');
  
  // Your scraping logic here
  
  await browser.close();
})();

If You’re Using TypeScript

Same idea, just cleaner syntax and full type support:

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

puppeteer.use(StealthPlugin());

// Rest of your code with full type support

Heads up: Don’t forget to register the stealth plugin with puppeteer.use() before launching your browser. That step’s non-negotiable.

Step 3: Test Your Stealth Setup

Before you dive into scraping the site you’re targeting, it’s a smart move to check that your stealth mode is actually doing its job. Here’s how you can test it:

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

puppeteer.use(StealthPlugin());

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  
  // Test on bot detection site
  await page.goto('https://bot.sannysoft.com');
  await page.screenshot({ path: 'bot-test.png', fullPage: true });
  
  // Check for common detection points
  const detectionResults = await page.evaluate(() => {
    return {
      webdriver: navigator.webdriver,
      headless: navigator.headless,
      userAgent: navigator.userAgent,
      plugins: navigator.plugins.length
    };
  });
  
  console.log('Detection test results:', detectionResults);
  await browser.close();
})();

What You’re Hoping to See

If things are set up right, you should notice:

  • navigator.webdriver returns undefined
  • There’s at least one plugin listed (shouldn’t be empty)
  • User agent doesn’t say “HeadlessChrome”
  • Bot detection pages give you a green light across the board

Step 4: Apply Advanced Evasion Techniques

Still getting caught? Time to level up. These techniques help refine and customize your stealth approach.

Pick and Choose Your Evasions

Instead of enabling every evasion (some can slow things down), you can manually select the ones you need:

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

// Use specific evasions only
const stealth = StealthPlugin();
stealth.enabledEvasions.delete('user-agent-override');
stealth.enabledEvasions.delete('media.codecs');

puppeteer.use(stealth);

Add Your Own Patches

You can patch browser properties yourself for finer control:

// Patch navigator.platform
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'platform', {
    get: () => 'MacIntel'
  });
});

// Patch hardware concurrency
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'hardwareConcurrency', {
    get: () => 8
  });
});

// Patch device memory
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'deviceMemory', {
    get: () => 16
  });
});

Behave Like a Human

Want to fly under the radar? Move like a real person:

// Add random delays between actions
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// Simulate human mouse movements
async function humanLikeClick(page, selector) {
  const element = await page.$(selector);
  const box = await element.boundingBox();
  
  await page.mouse.move(
    box.x + box.width / 2,
    box.y + box.height / 2,
    { steps: 10 }
  );
  
  await delay(100 + Math.random() * 200);
  await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
}

// Random scrolling behavior
async function humanScroll(page) {
  await page.evaluate(() => {
    const scrollHeight = document.body.scrollHeight;
    const currentPosition = window.scrollY;
    const randomScroll = Math.random() * 300 + 100;
    
    window.scrollBy({
      top: randomScroll,
      behavior: 'smooth'
    });
  });
  
  await delay(1000 + Math.random() * 2000);
}

Rotate Proxies

A fixed IP? That’s asking to be blocked. Rotate through proxies to reduce detection:

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

puppeteer.use(StealthPlugin());

async function launchWithProxy(proxyUrl) {
  const browser = await puppeteer.launch({
    headless: true,
    args: [
      `--proxy-server=${proxyUrl}`,
      '--no-sandbox',
      '--disable-setuid-sandbox'
    ]
  });
  
  return browser;
}

// Rotate through proxy list
const proxies = ['proxy1.com:8080', 'proxy2.com:8080'];
let currentProxy = 0;

async function getNextBrowser() {
  const proxy = proxies[currentProxy % proxies.length];
  currentProxy++;
  return launchWithProxy(proxy);
}

Step 5: Implement Alternative Solutions When Stealth Fails

Sometimes stealth isn’t enough—especially when you’re up against tools like Cloudflare. If that’s the case, try these workarounds:

1. Start with Simple HTTP Requests

Often, a regular HTTP request can get the job done with far less fuss:

const axios = require('axios');

// Often faster and less detectable than browser automation
async function lightweightScraping(url) {
  try {
    const response = await axios.get(url, {
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Language': 'en-US,en;q=0.5',
        'Accept-Encoding': 'gzip, deflate',
        'Connection': 'keep-alive',
        'Upgrade-Insecure-Requests': '1'
      }
    });
    
    return response.data;
  } catch (error) {
    console.error('Request failed:', error.message);
    // Fall back to Puppeteer if needed
  }
}

2. Try Playwright with Stealth

Playwright has built-in tools to help dodge detection:

const { chromium } = require('playwright-extra');
const stealth = require('puppeteer-extra-plugin-stealth')();

// Playwright can use Puppeteer plugins!
chromium.use(stealth);

async function stealthPlaywright() {
  const browser = await chromium.launch({
    headless: false,
    args: ['--disable-blink-features=AutomationControlled']
  });
  
  const context = await browser.newContext({
    viewport: { width: 1920, height: 1080 },
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  });
  
  const page = await context.newPage();
  // Your scraping logic
}

3. Use a Custom Browser Profile

Run your bot in a persistent user profile to mimic real browsing:

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

puppeteer.use(StealthPlugin());

// Use a real browser profile for better fingerprinting
async function launchWithProfile() {
  const browser = await puppeteer.launch({
    headless: false,
    userDataDir: './chrome-profile',
    args: [
      '--disable-blink-features=AutomationControlled',
      '--exclude-switches=enable-automation',
      '--disable-features=IsolateOrigins,site-per-process'
    ]
  });
  
  return browser;
}

4. Use a Web Scraping API

For heavily guarded sites, sometimes it’s best to let the pros handle it:

// Example using a web scraping API
const axios = require('axios');

async function robustScraping(targetUrl) {
  const apiUrl = 'https://api.scrapingservice.com/v1/scrape';
  
  const response = await axios.post(apiUrl, {
    url: targetUrl,
    render_js: true,
    premium_proxy: true,
    country: 'US'
  }, {
    headers: {
      'API-Key': 'your-api-key'
    }
  });
  
  return response.data;
}

Common Mistakes to Avoid

Mistake 1: Importing From the Wrong Package

If you’re using puppeteer instead of puppeteer-extra, the stealth plugin won’t work. Always import correctly.

Mistake 2: Reusing the Same Fingerprints

Sites can catch on fast if every request looks exactly the same. Switch up user agents, screen sizes, and device properties.

Mistake 3: Ignoring Rate Limits

Even the best stealth setup won’t help if you hammer a site with hundreds of requests per minute. Slow things down.

Final Thoughts

Puppeteer Stealth is a solid line of defense against basic bot detection, but it’s not a one-size-fits-all solution. The best scraping strategies start simple, scale gradually, and stay adaptive. Here’s the takeaway:

  1. Try HTTP requests before jumping to a browser
  2. Use automation when necessary, but not before
  3. Layer your evasion tactics for best results
  4. Follow ethical scraping practices
  5. When it’s too much work—consider using an API

Scraping gets tougher all the time, so the more flexible you are, the better your chances of staying ahead.

Marius Bernard

Marius Bernard

Marius Bernard is a Product Advisor, Technical SEO, & Brand Ambassador at Roundproxies. He was the lead author for the SEO chapter of the 2024 Web and a reviewer for the 2023 SEO chapter.