How to take a full-page screenshot

The whole page, not just the part you can see. Three ways to do it — in a browser, in code, over an API — and the four things that quietly break it.

In a browser, with no tools at all

If you need one screenshot once, stop here. Every major browser ships this and none of them advertise it.

These are genuinely good. They use the browser's own rendering, they cost nothing, and for a bug report or a design review they are the right answer. They stop being the right answer the moment you need the same shot every day, or a hundred of them, or one taken on a server with no screen attached.

In code, with Playwright or Puppeteer

Both expose it as one option. The honest version is that the option is the easy part and the browser is the rest.

Playwright
const { chromium } = require('playwright');

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'page.png', fullPage: true });
await browser.close();

Puppeteer is the same call with puppeteer.launch() instead. What neither snippet shows is the operational cost: a headless Chromium is about half a gigabyte of memory, it cold-starts in two to three seconds, and running several at once on one box will exhaust it. If this is going in a Lambda you will also need a queue in front of it, because concurrent invocations each want their own browser.

That is a reasonable trade when screenshots are your product. It is a poor one when they are a feature inside something else.

Over an API, in one call

getSnap.dev
curl -X POST https://api.getsnap.dev/v1/screenshot \
  -H "X-API-Key: sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "full_page": true,
    "lazy_load": true,
    "remove_popups": true
  }'

No browser to run, no queue to maintain. Every screenshot API does this much; the difference between them is what happens when the page does not cooperate, which is the rest of this page.

The four things that break it

1. Lazy-loaded images, so the bottom is blank

This is the most common complaint and the least obvious cause. Images with loading="lazy", or any of the JavaScript equivalents, are only fetched when they come near the viewport. A full-page capture does not scroll — it renders the whole document in one pass — so nothing below the first screen is ever requested, and you get a tall image with grey rectangles where the content should be.

The fix is to scroll the page first and wait for the requests to settle. In Playwright that is a loop that scrolls by a viewport at a time; here it is one option:

lazy loading
{ "url": "...", "full_page": true, "lazy_load": true, "wait_for_network_idle": true }

2. Sticky headers and floating widgets

Anything with position: fixed is trouble. Tools that work by taking one viewport-sized shot, scrolling, and stitching the results will paint that element into every single shot, so the header marches down the image a dozen times. Modern Chromium captures the document in one pass and does not repeat it — but the element then sits frozen at the top, covering whatever is underneath, and a chat bubble in the bottom corner can end up stranded in the middle.

Hiding it is the reliable answer in both cases:

hiding fixed elements
{ "url": "...", "full_page": true,
  "hide_selectors": [".site-header", "#intercom-container"] }

hide_selectors sets visibility: hidden, which leaves the layout alone. remove_selectors deletes the node, which reflows the page — useful when you want the space back, wrong when removing it moves everything else.

3. Cookie banners and consent overlays

A consent dialog is the single most common reason an automated screenshot is useless: the content is there, dimmed behind a modal, and no amount of waiting will dismiss it. There are three different situations and they need different handling — block the banner before it renders, click the accept button, or press Escape on a dialog that listens for it.

consent
{ "url": "...", "full_page": true,
  "remove_popups": true, "block_ads": true,
  "click_accept": true, "press_escape": true }

remove_popups and block_ads are on by default here. click_accept is not, because clicking a consent button on someone else's behalf is a decision you should make deliberately rather than inherit from a default.

4. Pages with no bottom

Two versions of this. An infinite-scroll feed genuinely has no end, so an auto-scrolling capture will keep going until something times out. And even a page that does end can be too tall to render: Chromium cannot allocate a texture higher than 16,384 pixels, and beyond roughly that point captures start coming back truncated or partly blank.

Decide the limit yourself instead of discovering it:

capping height
{ "url": "...", "full_page": true, "full_page_max_height": 12000 }

Full-page screenshots for AI vision models

If the screenshot is going to a vision model rather than a human, height is a harder constraint than it looks. The current generation caps out around 8,000 pixels. Hand one a 15,000-pixel capture and it is either rejected outright or downscaled until the text is unreadable — which is worse, because it fails quietly and the model answers anyway.

The answer is to slice the capture into overlapping tiles and send them in order. The overlap matters: without it a sentence can be cut in half across a seam and neither tile contains it.

slicing
{
  "url": "https://example.com",
  "full_page": true,
  "full_page_slices": true,
  "full_page_slice_height": 4000,
  "full_page_slice_overlap_height": 200
}

The response carries a list of slice URLs instead of one image. Of the five screenshot APIs we track, only getSnap.dev and ScreenshotOne do this — checked against their own documentation on 25 October 2026, and written up in the sourced comparison.

Which of the three to use

SituationUse
One screenshot, right now, for a bug reportThe browser. Firefox's right-click menu is the quickest.
A handful, scripted, on your own machinePlaywright. You already have the memory and the display.
On a server, repeatedly, or in a productAn API. The browser is the part you do not want to operate.
Thousands, with heavy customisation per captureYour own Chromium fleet, and accept the operational cost.

Frequently asked questions

Why is the bottom of my full-page screenshot blank or grey?

Almost always lazy-loaded images. The browser only loads them when they approach the viewport, and a full-page capture never scrolls, so nothing below the fold is requested. Scroll before capturing, or turn on an auto-scroll option if your tool has one.

Why does my header repeat all the way down the image?

Older tools capture one viewport at a time and stitch the results, and anything position: fixed is painted into every shot. Modern Chromium does not repeat it, but the element can still sit frozen at the top covering content. Hiding it by selector works in both cases.

Is there a maximum height?

In practice yes — around 16,384 pixels, which is Chromium's texture limit. Past that, captures truncate or come back partly blank. Set a cap deliberately.

Can I take a full-page screenshot of a page behind a login?

Yes, by sending the session cookie with the request. Treat that cookie as a credential: it grants whatever the logged-in user can do, so send it to a service you are willing to trust with the account.

Do I need an API, or will the browser do?

For one screenshot the browser is faster and free, and this page says so at the top. An API earns its place when you need the same capture repeatedly, from a machine with no display, at a size and device you choose, with consent banners handled the same way every time.

Try it on a page that has been breaking

100 captures a month, free and permanent, no card. Every option on this page works on the free plan — we meter credits, not features. There is also a no-signup tool if you would rather not take our word for it.

Get a free API key