Set Puppeteer’s Browser Window Size
Learn when to use Puppeteer viewport, content-area, window-bounds, and headless screen sizing APIs, with runnable examples and fixes.
Puppeteer has several different size controls. Use page.setViewport() for the page’s layout viewport, page.resize() for a browser window’s content area, browser.setWindowBounds() for outer window position and dimensions, and launch flags such as --window-size for headless screen setup.
The most common case is setting a page viewport:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.setViewport({
width: 1080,
height: 1024,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false
});
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
await page.screenshot({path: 'example.png', fullPage: false});
await browser.close();
Puppeteer’s documented default viewport is 800 by 600. A viewport controls the page’s layout and rendering area; it does not necessarily equal the operating-system window’s outer dimensions.
Choose the geometry you actually need
| Goal | API or flag | When to use it |
|---|---|---|
| Page layout and responsive breakpoints | page.setViewport() |
Testing mobile, tablet, and desktop layouts or taking screenshots |
| Browser content area | page.setViewport(null), then page.resize() |
Requesting a specific window.innerWidth and window.innerHeight |
| Outer window position, size, or state | browser.getWindowBounds() and browser.setWindowBounds() |
Headful automation that needs window placement or maximized/minimized state |
| Headless screen configuration | --window-size and, when needed, --screen-info |
Setting launch-time screen dimensions for headless Chrome |
Set the page viewport
page.setViewport() is the normal solution for screenshots and responsive testing. It changes the emulated page viewport, so CSS media queries and JavaScript viewport values respond to the requested dimensions.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({width: 1440, height: 900});
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
const dimensions = await page.evaluate(() => ({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
devicePixelRatio: window.devicePixelRatio
}));
console.log(dimensions);
await page.screenshot({path: 'desktop.png'});
await browser.close();
Viewport options
widthandheightare CSS pixels.deviceScaleFactorcontrols the emulated device pixel ratio. A value of 2 produces retina-style output and increases image dimensions and memory use.isMobileenables mobile emulation behavior.hasTouchenables touch input emulation.
Set the viewport before navigation when the initial responsive layout matters. If you change it after navigation, the page may need to recalculate layout and rerender components.
Resize the browser content area
Puppeteer’s window-management guide uses page.resize() to request a browser window whose content area has a specified size:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
// Remove Puppeteer’s default viewport constraint first.
await page.setViewport(null);
await page.resize({contentWidth: 600, contentHeight: 400});
await page.goto('https://example.com');
const size = await page.evaluate(() => ({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight
}));
console.log(size);
await browser.close();
The requested content area is the part inside the browser window, excluding browser UI such as tabs and toolbars. The Puppeteer Page API marks Page.resize experimental, so verify the current API before upgrading Puppeteer. In the documented example, a 600 by 400 content request produced window.innerWidth and window.innerHeight of 600 by 400, while the outer height was 487 in that environment. Do not treat that outer-height difference as universal; browser chrome and the operating system change it.
Set outer window bounds and state
If you need the actual browser window’s position, outer width, outer height, or state, use the browser window-management methods. You first need the window ID for the page’s target.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
const cdp = await page.createCDPSession();
const {windowId} = await cdp.send('Browser.getWindowForTarget');
const before = await browser.getWindowBounds(windowId);
console.log('Before:', before);
await browser.setWindowBounds(windowId, {
left: 80,
top: 40,
width: 1280,
height: 900,
windowState: 'normal'
});
const after = await browser.getWindowBounds(windowId);
console.log('After:', after);
await browser.close();
Window bounds are different from a page viewport. The operating system may clamp coordinates or dimensions to the available screen, and browser chrome contributes to the outer size. Use this approach for headful workflows that arrange windows on a desktop, open a window maximized, or move it to a specific monitor.
Window states
The bounds object can include left, top, width, height, and a windowState such as normal, minimized, or maximized, subject to the current Puppeteer and Chrome versions.
Configure headless screen dimensions at launch
For launch-time headless configuration, pass Chrome flags through Puppeteer’s args option:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true,
args: [
'--window-size=1440,900'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'headless.png'});
await browser.close();
Puppeteer’s screen-configuration guide documents an 800 by 600 default headless screen when --screen-info is absent. When --window-size is supplied, the headless screen is as large as the requested window. --screen-info is headless-only; headful Chrome uses the physical platform screens.
const browser = await puppeteer.launch({
headless: true,
args: [
'--screen-info={"workAreaInsets":{"top":0,"left":0,"right":0,"bottom":0},"workArea":{"x":0,"y":0,"width":1920,"height":1080},"depth":24,"depthPerComponent":8,"isMonochrome":false,"deviceScaleFactor":1,"colorSpace":"srgb","label":"screen","internal":true}'
]
});
Use the exact screen-info format supported by the Chrome version in your environment. For most screenshot jobs, page.setViewport() is simpler and more portable.
Combine viewport and window sizing safely
Do not assume that setting one dimension changes every other dimension. A reliable sequence is:
- Choose whether the requirement is viewport, content area, outer bounds, or screen.
- Launch with screen flags only when launch-time headless behavior is required.
- Create the page and set its viewport before navigation.
- For content resizing, call
page.setViewport(null)beforepage.resize(). - For outer bounds, obtain the window ID and call
browser.setWindowBounds(). - Navigate, wait for the page state you need, then measure with
window.innerWidth,window.innerHeight,window.outerWidth, andwindow.outerHeight.
const measured = await page.evaluate(() => ({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
dpr: window.devicePixelRatio
}));
console.table(measured);
Responsive and screenshot recipes
Desktop screenshot
await page.setViewport({width: 1440, height: 900, deviceScaleFactor: 1});
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
await page.screenshot({path: 'desktop.webp', type: 'webp'});
Mobile layout
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true
});
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
await page.screenshot({path: 'mobile.png'});
Full-page capture
await page.setViewport({width: 1280, height: 800});
await page.goto('https://example.com', {waitUntil: 'networkidle2'});
await page.screenshot({path: 'full-page.png', fullPage: true});
A full-page screenshot extends beyond the current viewport. It does not mean that the browser’s outer window has become as tall as the document.
Common errors and fixes
| Symptom | Cause | Fix |
|---|---|---|
page.resize is not a function |
The installed Puppeteer version does not expose the experimental method, or the method is unavailable in the current mode. | Check the installed version and current Page API. Use page.setViewport() for screenshot layout, or window bounds through the supported browser API. |
| Viewport is correct but the desktop window is the wrong size | A viewport is page geometry, not outer window geometry. | Use browser.setWindowBounds() for outer bounds, or page.resize() for content size. |
window.outerWidth differs from the requested width |
Browser chrome and operating-system window management add or subtract pixels. | Measure both inner and outer dimensions and assert the one your workflow actually needs. |
| Changing the viewport has no visible effect | The page was already rendered, or CSS is not using responsive breakpoints. | Set the viewport before navigation, reload if necessary, and inspect computed styles and media-query behavior. |
| Headless output remains 800 by 600 | No launch-time screen or viewport override was applied. | Pass --window-size=WIDTH,HEIGHT or call page.setViewport() after creating the page. |
| Window position is ignored | The browser is headless, the desktop environment clamps it, or a window manager controls placement. | Run headful for OS placement, verify the window state, and allow for platform restrictions. |
| High-DPI screenshots are unexpectedly large | deviceScaleFactor multiplies physical pixels. |
Use a lower scale factor or resize the output after capture when file size matters. |
| Navigation finishes before images or fonts appear | networkidle2 does not guarantee every visual asset is ready on applications with long-lived connections. |
Wait for a specific selector, font readiness, or an application-defined ready signal before capturing. |
Performance, reliability, and cost considerations
- Viewport changes are lightweight, but navigation and rendering dominate screenshot time.
- Large viewports, high device scale factors, and full-page captures increase memory use and image encoding time.
- Headful window management depends on the operating system and desktop session; headless viewport emulation is usually more repeatable in CI.
- Pin Puppeteer and Chrome versions for visual regression suites, then recheck experimental APIs such as
Page.resizewhen upgrading. - Wait for deterministic page-ready conditions rather than using an arbitrary long delay.
- Reuse a browser process for batches of pages, but create isolated pages and close them when finished to control memory.
- For cost, self-hosted Puppeteer mainly costs compute, browser runtime, storage, and maintenance. A hosted screenshot API can remove browser setup and infrastructure work.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API when you need an image or PDF without managing Puppeteer and Chrome. It supports viewport and device options, full-page capture, element selectors, custom CSS and JavaScript, waits, blocking rules, cookies, headers, caching, async jobs, bulk capture, and PDF settings. See the ScreenshotNeo API documentation for all 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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await Bun.write('shot.webp', image);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. An MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
FAQ
What should I use for a normal screenshot?
Use page.setViewport({width, height}). It directly controls the page layout area.
Does setting the viewport resize the operating-system window?
No. Use page.resize() for a requested content area or browser.setWindowBounds() for outer window geometry.
Why does outer height include extra pixels?
Browser UI contributes to outer dimensions, and the amount varies by browser version, theme, platform, and window manager.
Can I use --screen-info in headful Chrome?
No. The documented screen-configuration mechanism is headless-only; headful Chrome uses physical platform screens.
Is Page.resize stable?
Puppeteer marks it experimental. Check the current Page API and test after version upgrades.


