Puppeteer vs. Urlbox: Screenshot Alternatives
Compare self-hosted Puppeteer with Urlbox’s managed API, including full-page behavior, edge cases, costs, and a practical ScreenshotNeo alternative.
Short answer: choose Puppeteer when you need browser-level control and are prepared to run the capture workflow yourself. Choose Urlbox when you want a hosted rendering service with documented screenshot and PDF options. For a managed screenshot API with consent banners and distracting widgets removed before capture, failed loads not billed, and an MCP server for AI agents, try ScreenshotNeo first.
Puppeteer and Urlbox solve related problems through different operating models. Puppeteer is a JavaScript library for automating Chrome and Firefox through the Chrome DevTools Protocol and WebDriver BiDi. Its documented uses include screenshots, PDF generation, navigation, UI testing, and performance analysis. Urlbox is a hosted service that accepts rendering requests for screenshots, PDFs, videos, extracted text, HTML, and metadata. [Chrome for Developers] [Urlbox documentation]
At a glance
| Question | Puppeteer | Urlbox |
|---|---|---|
| Where does rendering run? | In your application or infrastructure, using a browser you launch and control. | In Urlbox’s managed rendering service. |
| Control level | Very high: browser context, page events, JavaScript, network interception, authentication, and custom orchestration. | Request-based controls documented by the service, including viewport, full-page, element capture, delays, and advanced render options. |
| Operational work | You own browser versions, concurrency, timeouts, queues, isolation, retries, storage, and monitoring. | The service handles browser execution; you integrate its synchronous or asynchronous request modes. |
| Best fit | Complex workflows, authenticated sessions, UI automation, tests, and cases requiring custom browser logic. | Teams that want URL or HTML rendering without operating browser workers. |
| Evidence available | Official documentation establishes capabilities, not a universal speed or quality result. | Official documentation and pricing establish features and plans, not an independent head-to-head benchmark. |
How Puppeteer screenshot capture works
A basic Puppeteer flow launches a browser, creates a page, navigates to a URL, waits for the desired loading state, and writes an image. The following script is complete and runnable with Node.js.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true,
// In containers, add the flags required by your runtime if needed.
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', {
waitUntil: 'networkidle2',
timeout: 60_000
});
await page.screenshot({
path: 'example.png',
fullPage: true,
type: 'png'
});
} finally {
await browser.close();
}
Install and run it with:
npm install puppeteer
node screenshot.mjs
Important Puppeteer options
- Viewport: set width, height, and device scale factor before navigation when responsive layout matters.
- Loading: use
waitUntilsuch asdomcontentloaded,load, ornetworkidle2, then add an explicit selector wait for application content. - Full page:
fullPage: truecaptures the document’s full height, but pages with lazy loading or infinite scrolling need additional scrolling logic. - Element capture: wait for a selector and call
elementHandle.screenshot(). - Formats: PNG, JPEG, and WebP are available through the screenshot API; JPEG and WebP support quality settings.
- PDF: use
page.pdf()with paper format, margins, landscape mode, and print background settings. - Browser context: create separate contexts for isolation between users, cookies, and authentication sessions.
- Network control: intercept requests to block ads, trackers, or unwanted resource types, but verify that required application resources still load.
Waiting for application state
Network idle is not a guarantee that the page is visually ready. A single analytics connection, WebSocket, polling request, or delayed image can keep the network busy or make it appear idle too early. Prefer a page-specific readiness condition when possible:
await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-screenshot-ready]', { timeout: 30_000 });
await page.screenshot({ path: 'dashboard.png', fullPage: true });
Cookies, popups, and custom state
Puppeteer gives you control, but you must implement the behavior. Set cookies before navigation, inject local storage through page.evaluateOnNewDocument, click consent buttons, and hide known overlays with CSS or DOM actions. Build these steps for each site or design a reusable site adapter.
How Urlbox differs
Urlbox exposes rendering through hosted requests. Its documentation describes render links, synchronous JSON API calls, and asynchronous JSON API calls, plus options for viewport sizing, delays, full-page screenshots, selector-based screenshots, and waiting for elements. [Urlbox documentation]
The practical difference is where the browser workflow lives. With Puppeteer, your code owns the browser lifecycle and can react to every page event. With Urlbox, your application submits a render request and consumes the resulting image or response. That can reduce infrastructure work, while giving you the controls and limits defined by the service.
Full-page screenshots and difficult pages
Full-page output depends on page behavior more than on the API name. Lazy-loaded images, sticky headers, cookie dialogs, animations, infinite scroll, and unusually tall sections can all change the result.
Urlbox stitch mode and native mode
Urlbox documents two full-page modes. Its default stitch mode scrolls through the page, triggers animations and lazy loading, freezes fixed or sticky elements, captures multiple sections, and stitches them together. The documented native mode uses browser-native full-page capture; Urlbox describes it as faster but says it may not work well on every site. [Urlbox full-page documentation]
Urlbox also documents practical edge cases:
- Sticky headers and footers can repeat between sections. Urlbox uses heuristics to capture most fixed elements once;
freeze_fixedcan disable that behavior. - Scrolling can trigger cookie banners or modals. The documented controls include hiding cookie banners, clicking an accept control, blocking ads, and blocking selected domains.
- Infinite-scroll pages can continue forever. Urlbox documents a default limit of three detected sections unless configured otherwise.
- Pages with full-height backgrounds may require viewport and section-height adjustments.
- Selector capture and selector waits should be tested against representative pages because a missing selector can fail the request when the relevant option is enabled.
Puppeteer strategies for the same edge cases
- Wait for a stable selector that represents the finished page.
- Scroll incrementally to trigger lazy content, with a maximum scroll count for infinite pages.
- Hide or click consent and modal elements before capture.
- Disable animations during the screenshot window with injected CSS.
- Capture a representative set of short, long, authenticated, and JavaScript-heavy pages before selecting defaults.
await page.addStyleTag({
content: `*, *::before, *::after {
animation: none !important;
transition: none !important;
caret-color: transparent !important;
}`
});
for (let i = 0; i < 20; i++) {
const reachedBottom = await page.evaluate(() => {
window.scrollBy(0, window.innerHeight);
return window.scrollY + window.innerHeight >= document.documentElement.scrollHeight;
});
if (reachedBottom) break;
await new Promise(resolve => setTimeout(resolve, 250));
}
await page.screenshot({ path: 'long-page.png', fullPage: true });
Which should you choose?
| Your requirement | Likely fit | Reason |
|---|---|---|
| Browser automation, UI testing, or performance inspection in the same workflow | Puppeteer | It is a general browser automation library, not only a screenshot endpoint. |
| Custom login flows, multi-step interactions, or page-specific JavaScript | Puppeteer | Your code can control each browser event and state transition. |
| URL-to-image rendering without running browser workers | Urlbox | It provides a hosted rendering service with synchronous and asynchronous requests. |
| Full-page pages with lazy loading or sticky elements | Either, after testing | Puppeteer gives control; Urlbox documents stitch and native modes with different trade-offs. |
| Variable batch workloads | Measure both against your pages | The reviewed sources do not provide a controlled performance benchmark. |
Cost, volume, and reliability
Self-hosted Puppeteer has no single per-render price. Your total depends on browser compute, memory, storage, queueing, engineering time, monitoring, upgrades, retries, and concurrency. Model those inputs from your own workload instead of treating the library as free infrastructure.
Urlbox’s pricing page, accessed in 2026, lists Lo-Fi at $19 per month for up to 2,000 renders, Hi-Fi at $49 for up to 5,000, Ultra at $99 for up to 15,000, Business at $498 with a base plus additional per-1,000-render pricing, and Enterprise starting at $3,000 per month. Prices exclude VAT and may vary by region or change over time. The same page says failed requests are not charged, render-link screenshots are cached for 30 days, and a TTL or force option can override cache behavior; cached render-link requests do not count against monthly quota, while first-time renders and REST API calls do. Verify current terms before purchasing. [Urlbox pricing]
For Puppeteer, reliability comes from engineering controls: isolate browser contexts, cap concurrency, enforce navigation and overall job timeouts, retry only transient failures, record the final URL and page errors, and close every browser in a finally block. For any managed service, record request IDs, response headers, status codes, and the rendered result so that failures can be diagnosed without blindly retrying.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Blank or partly rendered image | Capture occurred before application content or fonts loaded. | Wait for a readiness selector, add a bounded delay, and verify required requests completed. |
| Lazy images missing | The page never reached the image regions. | Scroll in steps before capture or use a full-page mode that loads lazy content. |
| Cookie banner covers content | Consent UI was not handled. | Click or hide the banner in Puppeteer; use the documented cookie controls in Urlbox; test region-specific variants. |
| Repeated sticky header | Stitching captured a fixed element in multiple sections. | Adjust fixed-element handling, or use native capture after testing. |
| Infinite page never finishes | Scroll-triggered loading has no natural end. | Set a maximum scroll count or section limit and capture a defined stopping point. |
| Navigation timeout | Slow origin, blocked resource, or a page that never reaches the chosen wait state. | Use a realistic timeout, wait for a selector instead of network idle, and inspect failed requests. |
| Browser crashes under load | Too many concurrent pages or insufficient memory. | Limit concurrency, recycle browsers, and measure memory per page. |
| Selector capture fails | The selector is missing, delayed, or inside an iframe or shadow root. | Wait for it explicitly and handle iframe or shadow DOM boundaries. |
Or skip the browser setup
ScreenshotNeo is the managed alternative to try first for website screenshots: it removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; and every response reports its page verdict and billing status through X-Page-Verdict and X-Billed headers. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Use the API shown in the ScreenshotNeo documentation:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo supports full-page capture with lazy images loaded, CSS element capture, dark mode, device presets and custom viewports, retina scale, PDF options, custom CSS and JavaScript, clicks, selector waits, delays, network-idle waits, blocking controls, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage reporting, and an OpenAPI specification. Its parameter names also accept the names used by other screenshot APIs, which helps when switching.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account.
FAQ
Is Puppeteer an alternative to Urlbox?
They are alternatives for some screenshot workloads, but they are different products. Puppeteer is a browser automation library; Urlbox is a hosted rendering API.
Which one is faster?
The reviewed sources do not provide a controlled benchmark. Test your representative URLs, viewport sizes, wait conditions, and output formats.
Can Puppeteer capture PDFs?
Yes. PDF generation is one of Puppeteer’s documented browser automation uses, alongside screenshots and UI testing.
Does Urlbox handle infinite scroll?
Urlbox documents a default limit of three sections for detected infinite-scroll pages unless configured otherwise. Confirm the current option and behavior in its documentation.
When should I use a screenshot API instead of a browser library?
Use a managed API when you want request-based rendering and do not want to operate browser workers. Use a library when your workflow needs direct browser control or broader automation.
