Puppeteer Full-Page Screenshot Clipped Horizontally: How to Fix It
Find out whether horizontal clipping comes from page layout, viewport-relative CSS, or a nested scroller, then choose the right Puppeteer capture method.
A Puppeteer full-page screenshot clipped horizontally can have several causes: the document is wider than the viewport, the page uses viewport-relative CSS, or the content is inside a nested scrolling element. Compare a viewport screenshot with a full-page screenshot, inspect document dimensions and overflow, then use full-page capture, a bounded clip, or an element screenshot according to what you need to capture.
The exact fix depends on the page, screenshot options, and installed Puppeteer and Chrome versions. Start by diagnosing the geometry instead of assuming that fullPage: true will capture every scrollable area.
1. Compare viewport and full-page captures
Capture the same page state both ways. If both images clip at the same point, investigate the page layout and viewport. If the viewport screenshot looks right but the full-page image differs, investigate full-page geometry and CSS that depends on viewport dimensions.
await page.screenshot({ path: 'viewport.png' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
Use page.viewport() to check the viewport settings configured in Puppeteer. It reports those settings; it does not verify the actual page viewport. Record your Puppeteer and Chrome versions with the reproduction, since version history can affect screenshot behavior.
2. Inspect document width and overflowing elements
Compare the document’s visible width with its scrollable width. A larger scrollWidth is a clue that some content extends beyond the visible document area. The following measurements are diagnostic DOM checks, not guarantees about screenshot output.
const geometry = await page.evaluate(() => {
const doc = document.documentElement;
const wideElements = [...document.querySelectorAll('body *')]
.map((element) => {
const rect = element.getBoundingClientRect();
const style = getComputedStyle(element);
return {
tag: element.tagName,
id: element.id,
className: typeof element.className === 'string' ? element.className : '',
left: rect.left,
right: rect.right,
width: rect.width,
overflowX: style.overflowX,
};
})
.filter((item) => item.right > doc.clientWidth || item.left < 0);
return {
clientWidth: doc.clientWidth,
scrollWidth: doc.scrollWidth,
innerWidth: window.innerWidth,
wideElements: wideElements.slice(0, 30),
};
});
console.log(geometry);
Inspect the reported elements and the page’s horizontal overflow. If an element is wider than intended, decide whether the page should constrain it, wrap its contents, or intentionally include the extra width. Avoid applying a global CSS override before identifying the element: it can hide meaningful content or change the layout you meant to capture.
3. Check nested scrolling regions
A document-level full-page capture does not necessarily include the scrollable contents of an element with overflow: auto or overflow: scroll. That element has its own scroll area. A historical Puppeteer issue describes a central scrolling region whose content was not included in the document-wide image; treat it as a diagnostic example rather than proof of current behavior. See Puppeteer issue #746.
Locate the container and inspect its dimensions and overflow settings:
const scrollers = await page.evaluate(() =>
[...document.querySelectorAll('body *')]
.map((element) => {
const style = getComputedStyle(element);
return {
tag: element.tagName,
id: element.id,
className: typeof element.className === 'string' ? element.className : '',
clientWidth: element.clientWidth,
scrollWidth: element.scrollWidth,
overflowX: style.overflowX,
overflowY: style.overflowY,
};
})
.filter((item) =>
(item.overflowX === 'auto' || item.overflowX === 'scroll' ||
item.overflowY === 'auto' || item.overflowY === 'scroll') &&
(item.scrollWidth > item.clientWidth)
)
);
console.log(scrollers);
If the needed content lives in a nested scroller, scroll that element itself and capture the intended area with a method designed for the target. Do not assume that document-level fullPage capture traverses every inner scroller.
4. Choose the capture method that matches the target
| Target | Method | What to check |
|---|---|---|
| The whole document | page.screenshot({ fullPage: true }) |
Check document overflow and whether content is in a nested scroller. |
| A known rectangle | page.screenshot({ clip: { x, y, width, height } }) |
Make the clip coordinates and dimensions match the intended output region. |
| One element | elementHandle.screenshot() |
Check whether overflowing descendants or inner scrollers need separate handling. |
| A layout-width problem | Correct the page CSS or adjust the viewport deliberately | Recheck responsive behavior and any resize-dependent page logic. |
Puppeteer documents fullPage as requesting a full-page capture and clip as specifying the capture region. Its screenshot options reference also documents captureBeyondViewport; the default is false when there is no clip and true when a clip is set. See the ScreenshotOptions reference.
Capture the full document
await page.screenshot({ path: 'page.png', fullPage: true });
Capture a rectangle
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 1200, height: 900 },
});
Choose the rectangle based on the target coordinates and desired output. If the clip cuts off content, inspect its x, y, width, and height alongside the page geometry. Verify behavior with your installed version.
Capture one element
const element = await page.waitForSelector('.report');
if (!element) throw new Error('Could not find .report');
await element.screenshot({ path: 'report.png' });
Puppeteer’s guide says an element screenshot attempts to scroll the element into view if it is hidden. That does not mean every oversized element or internally scrolling descendant will be captured in full. See the Puppeteer Screenshots guide.
5. Investigate viewport-relative CSS
Styles using vw or vh can make the layout depend on viewport dimensions. Compare computed layout before and after a full-page capture if that kind of CSS is present. Puppeteer issue #703 records a historical report that full-page capture width affected viewport-relative CSS. A viewport-width workaround appeared in that discussion, but it is not current official guidance. Reproduce the behavior with your own page and installed versions before changing the capture strategy.
Changing viewport size can change responsive layout or trigger page resize logic. If you test a different viewport, compare the resulting page geometry and appearance against the intended capture.
6. Account for lazy content and very tall pages
Lazy-loaded content may not be present until it is brought into view. A full-page screenshot should not be treated as proof that all lazy assets or content in nested scrollers have loaded. If required content is missing, make the page load it as part of your capture workflow, then check the element and document dimensions again.
Very large screenshots can fail for reasons tied to browser and Puppeteer versions. A historical report described crashes and clipping with Puppeteer 2.0.0 and Chromium 79. Its reported pixel estimate is not a current Chromium limit. See Puppeteer issue #5300. The Puppeteer changelog also records a fix in version 22.12.1, dated 2024-06-26, to reset the viewport after a full-page screenshot when defaultViewport is null. See the Puppeteer changelog. Check your installed version and test large captures under the conditions you plan to use.
7. Troubleshooting common symptoms
| Symptom | Likely cause | Next step |
|---|---|---|
| Viewport and full-page images clip at the same horizontal point | Page layout or viewport width | Compare clientWidth, scrollWidth, and wide element bounds. Correct the page layout if the overflow is unintended. |
| Viewport screenshot is correct, full-page image differs | Full-page geometry or CSS depending on vw/vh |
Compare computed dimensions and reproduce with the installed Puppeteer and Chrome versions. |
| Only a panel, table, or central region is incomplete | Content sits inside a nested scroller | Inspect that container’s own scroll dimensions and capture the target area deliberately. |
| A clipped rectangle is missing content | Clip coordinates or dimensions do not cover the intended region | Check x, y, width, and height against the target’s bounds. |
| Capture breaks after changing viewport size | Responsive layout or resize-dependent page behavior | Compare the page at the original and changed viewport; avoid treating a viewport change as a neutral screenshot fix. |
| Very tall capture crashes or clips | Large output or version-specific behavior | Record browser and Puppeteer versions, reduce the captured area if practical, and reproduce with a current supported setup before applying historical workarounds. |
8. Keep captures reliable and practical
- Record the environment: include Puppeteer and Chrome versions, configured viewport, screenshot options, and page dimensions with a reproduction.
- Capture a stable page state: wait for the content you need before measuring and taking the screenshot; recheck lazy content and nested scrollers explicitly.
- Keep the requested region bounded: when the task needs one panel or rectangle, capture that target instead of producing an unnecessarily large image.
- Recheck after CSS or viewport changes: responsive rules and resize handlers can change what the screenshot contains.
- Do not turn historical reports into universal rules: issue discussions can point toward a diagnosis, but confirm the behavior against the current page and versions.
The research basis gives no current screenshot performance benchmark or universal browser pixel limit. Cost depends on the infrastructure running your browser capture, which is outside Puppeteer’s screenshot options.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. For the simplest capture, make one GET request; see the ScreenshotNeo API documentation for options.
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)
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}`);
- Cookie banners are accepted and removed before capture; newsletter popups and chat widgets are removed too. Each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. The response says what happened in
X-Page-VerdictandX-Billedheaders. - An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- The Free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does fullPage: true capture every scrollable element?
No. It requests a full-page document capture. A nested element has its own scroll area and may need a separate capture approach.
Should I always set captureBeyondViewport to true?
No universal setting follows from the clipping symptom. Check the documented defaults and test the option with your target, clip, and installed version.
Is horizontal clipping always a Puppeteer bug?
No. It can result from page layout, viewport-dependent CSS, a nested scroller, clip bounds, or version-specific behavior. Diagnose those cases before applying an issue-specific workaround.
What should I include in a bug report?
Provide a minimal reproducible page, Puppeteer and Chrome versions, viewport settings, screenshot options, and measurements for the document and clipped element.


