ScreenshotNeo

BlogHow-to

How to Use Browserless Screenshots with Puppeteer

Connect Puppeteer to Browserless, capture and save a screenshot, troubleshoot incomplete pages, and choose between a remote browser session and a one-shot API.

By the ScreenshotNeo team4 October 20266 min read

To take a screenshot with Browserless and Puppeteer, install puppeteer-core, connect to a regional Browserless WebSocket endpoint using your API token, navigate to the page, and call page.screenshot(). Close the connection in a finally block so the remote session does not stay active after the capture.

1. Choose the right Browserless workflow

Use a Puppeteer connection when you need to interact with a page, wait for selectors, or perform several steps in one browser session. For one capture without page interaction, Browserless’s /screenshot REST endpoint can be simpler: send one request and save the returned image bytes.

Need Use
Click, inspect, or wait on page elements Puppeteer over WebSocket
Several operations in one browser session Puppeteer over WebSocket
One screenshot with no custom interaction Browserless screenshot REST endpoint

The Browserless endpoint below uses the SFO production region shown in its examples. Browserless documents regional endpoints; choose the hostname for your deployment and region rather than assuming this example applies everywhere. Browserless Puppeteer connection guide

2. Install Puppeteer Core and set your token

Use puppeteer-core because the browser runs remotely. The full puppeteer package downloads a local Chromium binary, which is unnecessary for this connection workflow.

npm install puppeteer-core
export BROWSERLESS_TOKEN="your-browserless-token"

Get the token from your Browserless account dashboard. Keep it in an environment variable or secret store; do not commit it to source control.

3. Capture a screenshot with Puppeteer

This runnable ES module connects, opens a page, navigates, writes a full-page PNG, and closes the remote browser connection even if navigation or capture fails.

import puppeteer from 'puppeteer-core';

const token = process.env.BROWSERLESS_TOKEN;
if (!token) throw new Error('Set BROWSERLESS_TOKEN before running this script.');

const browser = await puppeteer.connect({
  browserWSEndpoint: `wss://production-sfo.browserless.io?token=${encodeURIComponent(token)}`,
});

try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
  await page.goto('https://example.com/', {
    waitUntil: 'networkidle2',
    timeout: 60000,
  });
  await page.screenshot({ path: 'screenshot.png', fullPage: true, type: 'png' });
} finally {
  await browser.close();
}

Save this as screenshot.mjs and run node screenshot.mjs. After connecting, the usual Puppeteer page methods apply. Closing the browser ends the remote session; an unclosed session can remain active until timeout and may incur session time. Connection guide

4. Configure the screenshot

Format and quality

page.screenshot() supports a file path and capture settings including fullPage, type, quality, and clip. PNG is lossless; quality applies to lossy JPEG and WebP output.

// JPEG, full page, compressed quality
await page.screenshot({ path: 'page.jpg', type: 'jpeg', quality: 80, fullPage: true });

// Capture a rectangular area in the current viewport
await page.screenshot({
  path: 'region.png',
  type: 'png',
  clip: { x: 100, y: 120, width: 800, height: 500 },
});

Viewport versus full page

Set the viewport before navigation if the page’s layout depends on screen size. A regular screenshot captures the viewport; fullPage: true captures the full document. Very tall pages can require more time and memory. Full-page capture alone may not make lazy-loaded content appear; scroll the page first when the site loads content on demand.

Wait for dynamic content

networkidle2 is a useful starting point, but analytics, polling, and streaming connections can prevent a page from becoming idle. When the content you need is tied to a selector, wait for that selector instead:

await page.goto('https://example.com/report', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready="true"]', { timeout: 15000 });
await page.screenshot({ path: 'report.png', fullPage: true });

For a known animation or delayed render, wait for the relevant condition or a short explicit delay. Prefer a condition that reflects the content you need over a long arbitrary sleep.

Trigger lazy-loaded content

Scroll through the document before capturing if images or sections load as they approach the viewport. Browserless’s REST screenshot options also provide a scrollPage request setting for triggering lazy-loaded content.

await page.evaluate(async () => {
  const step = Math.max(400, window.innerHeight);
  for (let y = 0; y < document.body.scrollHeight; y += step) {
    window.scrollTo(0, y);
    await new Promise(resolve => setTimeout(resolve, 150));
  }
  window.scrollTo(0, 0);
});
await page.screenshot({ path: 'long-page.png', fullPage: true });

5. Browserless REST screenshot alternative

For a one-shot capture, Browserless’s screenshot REST endpoint accepts a URL (or raw HTML) and screenshot options, then returns image bytes. The documented request shape includes an options object; options include image type, full-page capture, quality, clipping, viewport-related settings, selector capture, waiting, and navigation configuration. Consult the current endpoint documentation for exact request and authentication details.

curl -X POST "https://production-sfo.browserless.io/screenshot?token=$BROWSERLESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"url":"https://example.com/","options":{"fullPage":true,"type":"png"}}' \
  --output screenshot.png

REST is convenient when the input is a URL and the output is just image bytes. Use Puppeteer when you need to keep a page session open and drive it. Browserless screenshot documentation and REST screenshot guide describe supported options. The endpoint returns a binary image, so save the response body directly rather than trying to parse it as JSON.

6. Troubleshoot incomplete or failed captures

Symptom Likely cause What to try
Connection fails or closes immediately Wrong token, endpoint, or region Check the token, confirm the current regional WebSocket hostname, and ensure the token is URL encoded.
Navigation times out The page never reaches the selected wait condition, or the site is slow Use an appropriate navigation condition such as domcontentloaded, raise the navigation timeout, then wait for the specific content selector.
Screenshot is blank or access denied The site may have returned a bot check, CAPTCHA, or access-denied page Inspect the page response and content. Browserless documents an optional /unblock endpoint for sites that need anti-bot handling; it is not a guarantee that every protected site can be captured.
Images or lower sections are missing Lazy loading or delayed rendering Scroll through the page to trigger loading, wait for relevant image or content selectors, then capture.
Capture ends before the page Viewport screenshot used instead of full-page capture Set fullPage: true, or use a clip when only a defined region is required.
Remote sessions remain active The browser connection was not closed on an error path Wrap page work in try/finally and call browser.close().
REST output cannot be opened Response bytes were handled as text or JSON Save the raw response body to a file with the matching extension and format.

7. Performance, reliability, and cost considerations

Remote browser work includes connection setup, page load, any explicit waits, and image encoding. Reuse one connected browser for related steps rather than opening a new session for each operation. Keep waits tied to page conditions, set realistic timeouts, and always close the connection in finally. Full-page images and high device scale factors produce larger outputs and may take longer to generate.

Capture reliability depends on the target page, its loading behavior, and any bot protections. A successful browser connection does not guarantee the site will return its intended content. Check the captured page when the result matters, especially if the page is protected or heavily dynamic. Browserless’s connection documentation notes that an open session may remain active until timeout and may incur billed session time; check Browserless’s current plan and billing documentation for applicable charges.

8. Or skip the browser setup

If you need a screenshot from a URL without managing a remote browser session, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF. See the ScreenshotNeo API documentation.

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

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for free screenshots.

9. FAQ

Do I need to install Chromium locally?

No. For the remote Browserless workflow, install puppeteer-core; the browser runs on Browserless.

Can I send HTML instead of a URL?

The Browserless screenshot REST endpoint supports a URL or raw HTML input. Use Puppeteer when you need to prepare or interact with a page through browser APIs.

Is the old chrome.browserless.io endpoint a good starting point?

No. The older BaaS v1 screenshot documentation says that version is no longer actively supported and points users to updated BaaS v2 or BrowserQL documentation. Follow current docs for endpoint and option details.

When should I use clip instead of full-page capture?

Use clip when you know the viewport coordinates and dimensions of the region you need. Use fullPage when the whole document is the desired output.