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.
- Chrome and Edge. Open DevTools, press
Ctrl+Shift+P(Cmd+Shift+Pon a Mac) to open the command menu, type screenshot, and choose Capture full size screenshot. - Firefox. Right-click anywhere on the page and pick Take Screenshot, then Save full page. No DevTools required, which makes it the friendliest of the three.
- Safari. Enable the Develop menu, open Web Inspector, find
the
<html>element in the Elements tab, right-click it and choose Capture Screenshot. Capturing the root element is what gets you the whole document.
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.
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
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:
{ "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:
{ "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.
{ "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:
{ "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.
{
"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
| Situation | Use |
|---|---|
| One screenshot, right now, for a bug report | The browser. Firefox's right-click menu is the quickest. |
| A handful, scripted, on your own machine | Playwright. You already have the memory and the display. |
| On a server, repeatedly, or in a product | An API. The browser is the part you do not want to operate. |
| Thousands, with heavy customisation per capture | Your 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