ScreenshotNeo

BlogEngineering

Why Puppeteer-Extra’s solveRecaptchas() Is Not a Function

Fix Puppeteer-Extra's solveRecaptchas() TypeError by registering the plugin, using the right instance, and handling existing pages correctly.

By the ScreenshotNeo team30 September 20266 min read

Why Puppeteer-Extra's solveRecaptchas() Is Not a Function

Direct answer: page.solveRecaptchas() is added by puppeteer-extra-plugin-recaptcha; it is not built into Puppeteer. In the commonly reported Apify example, a function defines puppeteer.use(RecaptchaPlugin(...)) but is never called. Register the plugin before launching the crawler, pass that same puppeteer-extra instance to the crawler, and make sure the page was created after the plugin hooks were installed.

The official plugin documentation also documents a second cause: reusing an existing about:blank page that was created before the plugin lifecycle ran. The method is absent on that page until its lifecycle hook is invoked.

1. Register the plugin before launching

Use one puppeteer-extra object throughout your program. Call .use() before launch(), before creating the crawler, and before obtaining pages.

The plugin must be registered before the page lifecycle begins.
The plugin must be registered before the page lifecycle begins.
const puppeteer = require('puppeteer-extra')
const RecaptchaPlugin = require('puppeteer-extra-plugin-recaptcha')

puppeteer.use(RecaptchaPlugin({
  provider: {
    id: '2captcha',
    token: process.env.TWOCAPTCHA_API_KEY
  }
}))

async function main() {
  const browser = await puppeteer.launch({ headless: true })
  const page = await browser.newPage()

  await page.goto('https://example.com', { waitUntil: 'networkidle2' })
  const result = await page.solveRecaptchas()
  console.log(result)

  await browser.close()
}

main().catch(error => {
  console.error(error)
  process.exitCode = 1
})

Install the packages with:

npm install puppeteer puppeteer-extra puppeteer-extra-plugin-recaptcha

A provider is required when you actually want the plugin to solve a challenge. Calling solveRecaptchas() on a page with no detected CAPTCHA is supported and resolves normally according to the plugin documentation.

2. The exact Apify integration mistake

A frequent pattern looks like this:

const puppeteer = require('puppeteer-extra')
const RecaptchaPlugin = require('puppeteer-extra-plugin-recaptcha')
const { PuppeteerCrawler } = require('crawlee')

function addPlugins() {
  puppeteer.use(RecaptchaPlugin({
    provider: {
      id: '2captcha',
      token: process.env.TWOCAPTCHA_API_KEY
    }
  }))
}

// The bug: addPlugins() is never called.

const crawler = new PuppeteerCrawler({
  launchContext: {
    launcher: puppeteer
  },
  async requestHandler({ page }) {
    await page.solveRecaptchas()
  }
})

crawler.run(['https://example.com'])

Call the setup function before constructing or running the crawler:

addPlugins()

const crawler = new PuppeteerCrawler({
  launchContext: {
    launcher: puppeteer
  },
  async requestHandler({ page }) {
    await page.solveRecaptchas()
  }
})

crawler.run(['https://example.com'])

The important details are registration order and instance identity. Do not register the plugin on one imported object and give Apify a different launcher.

3. Check the page lifecycle

After registration is confirmed, inspect how the failing page was created. The plugin hooks pages created through its normal lifecycle. A page that already existed, especially a reused about:blank tab, may not have been patched.

Preferred approach: create a new page after registration

puppeteer.use(RecaptchaPlugin({
  provider: { id: '2captcha', token: process.env.TWOCAPTCHA_API_KEY }
}))

const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto(targetUrl)
await page.solveRecaptchas()

When an existing page must be reused

The plugin README documents manually invoking the plugin’s onPageCreated lifecycle method for an existing page. Keep a reference to the plugin instance and call the hook before using solveRecaptchas():

const puppeteer = require('puppeteer-extra')
const RecaptchaPlugin = require('puppeteer-extra-plugin-recaptcha')

const recaptcha = RecaptchaPlugin({
  provider: { id: '2captcha', token: process.env.TWOCAPTCHA_API_KEY }
})
puppeteer.use(recaptcha)

const browser = await puppeteer.launch()
const [page] = await browser.pages()

await recaptcha.onPageCreated(page)
await page.goto('https://example.com')
const result = await page.solveRecaptchas()

Creating a fresh page is usually simpler. Treat the hook workaround as a targeted fix for intentionally reused pages, not as a requirement for every Apify version.

4. Distinguish a missing method from a solving failure

TypeError: page.solveRecaptchas is not a function means the property is not callable on that page object. It does not prove that a CAPTCHA exists, that your provider is unavailable, or that a token is invalid.

  1. Fix registration and page lifecycle until typeof page.solveRecaptchas === 'function'.
  2. Then verify provider credentials, account balance and provider availability.
  3. Inspect the returned result for captchas, filtered, solutions, solved and error.
const result = await page.solveRecaptchas()
console.dir(result, { depth: null })

if (result.error) {
  console.error('Captcha solving reported an error:', result.error)
}

The plugin reports solving errors in the result’s error field by default rather than necessarily throwing an exception.

5. Troubleshooting checklist

Symptom Likely cause Fix
solveRecaptchas is not a function immediately addPlugins() or equivalent setup was never called Call puppeteer.use(RecaptchaPlugin(...)) before launch or crawler creation.
Plugin appears configured, method still missing The crawler uses a different Puppeteer instance Pass the exact puppeteer-extra object on which .use() ran.
Only one reused tab fails The page existed before plugin hooks were attached Create a new page, or call the plugin’s onPageCreated(page) hook for that tab.
Method exists but solved is empty No supported CAPTCHA was detected, or the page changed Review captchas and filtered; verify the page URL and challenge type.
Result contains error Provider credentials, funds or provider service problem Check the provider ID, token, account status and provider logs.
Unsure whether hooks ran Insufficient startup diagnostics Run with DEBUG=puppeteer-extra,puppeteer-extra-plugin:*.

6. Debugging and version checks

Enable the namespaces recommended by the project:

DEBUG=puppeteer-extra,puppeteer-extra-plugin:* node crawler.js

On Windows PowerShell:

$env:DEBUG='puppeteer-extra,puppeteer-extra-plugin:*'; node crawler.js

Log the package versions and launcher path when registration looks correct. The original report is from October 2021 and uses Apify’s PuppeteerCrawler; it is a useful diagnostic example, not proof that every current package combination creates pages identically.

7. Reliability, performance and cost considerations

  • Register once during process startup. Repeatedly calling .use() while handling requests can create confusing plugin state.
  • Create pages through the configured launcher so every page receives the same hooks.
  • Wait for navigation and the challenge to appear before solving; solving too early can produce an empty result.
  • Keep provider credentials in environment variables, never in source control or page scripts.
  • Use the returned fields to record whether a CAPTCHA was found, filtered, solved or failed. This separates integration errors from provider errors.
  • CAPTCHA solving adds network latency because a third-party provider may need to return a token. Set crawler timeouts accordingly and avoid solving pages that do not contain a challenge.
  • Provider charges, limits and availability are separate from Puppeteer-Extra. The TypeError itself does not indicate a provider charge or outage.

8. Or skip the browser setup

If your goal is a clean image or PDF of a page rather than CAPTCHA automation, ScreenshotNeo provides a single HTTP request. It accepts the consent banner before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and each response reports the result through X-Page-Verdict and X-Billed headers.

ScreenshotNeo removes common overlays before returning the image.
ScreenshotNeo removes common overlays before returning the image.

See the ScreenshotNeo API documentation for all capture options.

cURL

curl -G 'https://api.screenshotneo.com/v1/shot' \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp

Python

import requests

r = requests.get(
    'https://api.screenshotneo.com/v1/shot',
    params={'access_key': 'YOUR_API_KEY', 'url': 'https://example.com'},
    timeout=90,
)
r.raise_for_status()
open('shot.webp', 'wb').write(r.content)

Node.js

import { writeFile } from 'node:fs/promises';

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned ${res.status}`);
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. It supports full-page and element capture, device presets, custom viewports, dark mode, JavaScript and CSS, waits, request blocking, headers, cookies, authentication, geolocation, caching, signed links, asynchronous jobs, bulk capture and PDF settings.

Create a free ScreenshotNeo account for 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

9. FAQ

Is solveRecaptchas() part of Puppeteer?

No. The method comes from puppeteer-extra-plugin-recaptcha after registration on a puppeteer-extra instance.

Do I need a provider if the page has no CAPTCHA?

The plugin’s normal flow allows calling the method when no CAPTCHA is present. A provider is needed when a challenge must be solved.

Does this error mean the CAPTCHA provider rejected my token?

No. A missing function is a local page/plugin integration problem. Provider errors occur after the method exists and are reported in the solving result.

Why does only an existing browser tab fail?

That tab may have been created before the plugin lifecycle hook ran. Create a new page or invoke onPageCreated(page) for the reused page.