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.

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.

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.
- Fix registration and page lifecycle until
typeof page.solveRecaptchas === 'function'. - Then verify provider credentials, account balance and provider availability.
- Inspect the returned result for
captchas,filtered,solutions,solvedanderror.
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.

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.


