Set Browser Window Bounds with Puppeteer
Set a Puppeteer browser window’s position, size, or state with setWindowBounds—and learn when to use resize or setViewport instead.
To change a browser window’s position, outer size, or state in Puppeteer, get its window ID with page.windowId() and call browser.setWindowBounds(windowId, bounds). Read the current bounds with browser.getWindowBounds(windowId). Use page.resize() for a requested content-area size, or page.setViewport() when you only need to change the page viewport. These APIs control different rectangles. Puppeteer’s window-management guide documents the bounds workflow.
1. Choose the rectangle you need to change
| Goal | API | What it changes |
|---|---|---|
| Move, resize, maximize, or restore a browser window | browser.setWindowBounds(windowId, bounds) |
Outer browser-window bounds and state |
| Set the page content area to a specific width and height | page.resize({contentWidth, contentHeight}) |
Browser window size adjusted to fit the requested content area; documented as experimental |
| Set page viewport dimensions or device metrics | page.setViewport(viewport) |
Page viewport, not a window position or a guaranteed outer-window rectangle |
| Emulate a known device | page.emulate(device) |
Device viewport and user agent |
In short, setWindowBounds is the direct answer when “window bounds” means the browser window’s position and outer dimensions. setViewport is usually the right choice for responsive-page screenshots because it sets the webpage’s viewport. Puppeteer notes that viewport changes involving mobile or touch settings can reload the page, and recommends setting the viewport before navigation when possible. See the setViewport API reference and emulate API reference.
2. Set position and size, then inspect the result
This runnable Node.js example uses Puppeteer’s bundled browser. It creates a window, obtains its ID, sets its position and dimensions, and reads back the reported bounds. Install Puppeteer with npm install puppeteer, save the code as window-bounds.mjs, and run node window-bounds.mjs.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: false,
});
try {
// Create a separate browser window. Puppeteer documents this option
// in its window-management guide.
const page = await browser.newPage({ type: 'window' });
const windowId = await page.windowId();
await browser.setWindowBounds(windowId, {
left: 100,
top: 80,
width: 1200,
height: 800,
});
const bounds = await browser.getWindowBounds(windowId);
console.log(bounds);
await page.goto('https://example.com');
// Keep the visible window open for inspection.
await new Promise(resolve => setTimeout(resolve, 5000));
} finally {
await browser.close();
}
The WindowBounds object uses left, top, width, and height for the rectangle. Its state can also be controlled with windowState. The setter returns a promise that resolves when the command completes; use the paired getter when your workflow needs to inspect the resulting bounds. Refer to the setWindowBounds API reference and guide examples.
Maximize and restore
await browser.setWindowBounds(windowId, { windowState: 'maximized' });
console.log(await browser.getWindowBounds(windowId));
await browser.setWindowBounds(windowId, { windowState: 'normal' });
console.log(await browser.getWindowBounds(windowId));
The documented guide also uses the fullscreen state in a fullscreen-element example. State changes depend on the browser and environment; inspect the returned bounds instead of assuming a particular screen size or frame offset.
3. Set the page content area instead
If the requirement says the page’s inner content area must be a particular size, use page.resize({contentWidth, contentHeight}). Puppeteer labels Page.resize experimental, so check the documentation for the Puppeteer version installed in your project. Its guide removes the default viewport first, then waits for the browser’s resize event before reading dimensions because the inner-window size is reported asynchronously.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: false });
try {
const page = (await browser.pages())[0];
await page.setViewport(null);
const resizeReported = page.evaluate(() => new Promise(resolve => {
window.addEventListener('resize', resolve, { once: true });
}));
await page.resize({ contentWidth: 600, contentHeight: 400 });
await resizeReported;
console.log(await page.evaluate(() => ({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
})));
} finally {
await browser.close();
}
The event wait must be registered before calling resize so a fast resize event is not missed. If the content dimensions matter, measure innerWidth and innerHeight after the event rather than deriving them from outer bounds. Browser UI dimensions vary; Puppeteer’s sample output is an example, not a universal frame-size formula. See the window-management guide and Page API reference.
4. Set only the viewport
For responsive layout checks or screenshots at a specific CSS viewport, set the viewport before loading the page. This does not position the outer browser window.
const page = await browser.newPage();
await page.setViewport({
width: 1280,
height: 800,
deviceScaleFactor: 1,
});
await page.goto('https://example.com');
await page.screenshot({ path: 'viewport.png' });
page.setViewport(null) resets the explicit viewport to its default behavior. When connecting to an existing browser, Puppeteer’s defaultViewport option defaults to 800×600 in the documented version; setting it to null removes that default viewport configuration. Set device emulation before navigation where possible so the site sees the intended metrics during its initial setup.
5. When the bounds operation is unavailable or surprising
- Window ID is required: obtain it from the page whose browser window you intend to control with
await page.windowId(). A page and a browser window are not interchangeable identifiers. - Use a window-backed page: the official guide creates a page using
browser.newPage({type: 'window'}). If the current target is a tab or a remote browser implementation does not support this window-management path, use an API supported by that environment or control the viewport instead. - Headless and remote environments differ: outer-window movement may not be meaningful in a headless session, container, or hosted browser. The API command can be accepted while the environment has no visible desktop window to move. For screenshot layout, set the viewport; for actual desktop automation, run a browser in an environment that exposes a window.
- Screen limits apply: requested coordinates or dimensions may be constrained by available screens and window manager behavior. Query
getWindowBoundsafter setting them. Avoid relying on fixed browser-chrome offsets. - Version matters: compare the installed Puppeteer version with the current window-management guide and API reference, especially for the documented experimental
Page.resizemethod.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
page.windowId is not a function or the call fails |
The installed Puppeteer version, page target, or browser connection does not expose the documented window-management API. | Check the installed version and target type; compare with the official guide. Use viewport sizing if only page dimensions matter. |
| The page looks resized, but the OS window did not move | setViewport changes page metrics rather than outer-window position. |
Get the window ID and call browser.setWindowBounds for an actual browser window. |
| The browser window changes, but content dimensions are wrong | Outer bounds include browser UI and may be affected by the platform. | Use page.resize({contentWidth, contentHeight}), then await a resize event and measure inner dimensions. |
| Resize event wait never resolves | The listener may have been added after resize, or the requested size caused no event in that environment. | Register the listener before resizing. Add a bounded timeout in production and inspect current dimensions if no event arrives. |
| Position or size differs from the requested values | Screen bounds, window manager, headless mode, or remote-browser support can constrain the result. | Read getWindowBounds after setting and verify the browser runs with a visible window on the intended display. |
| Page reloads after viewport configuration | Puppeteer notes some isMobile or hasTouch viewport changes can reload the page. |
Apply viewport or device emulation before navigation when possible. |
7. Performance, reliability, and cost
Changing bounds is a browser-control operation and does not itself load a page. For repeatable captures, set the viewport or bounds before navigation, wait for the condition your workflow needs, and inspect the resulting dimensions. Avoid arbitrary sleeps as the only synchronization mechanism when an event or explicit page condition is available. If using the experimental content resize API, keep version changes in mind and verify the measured inner size.
Local Puppeteer requires you to manage the browser process and its runtime environment. For a hosted screenshot workflow, ScreenshotNeo provides a website screenshot API and MCP server: one GET request returns an image or PDF, so you do not need to launch and manage a browser for that capture. Its clean-shot handling accepts cookie banners and removes known consent platforms, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. The response includes page-verdict and billing headers. The free tier includes 1,000 shots a month without a card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.
Or skip the browser setup
For a website screenshot rather than desktop-window automation, make a single request. See the ScreenshotNeo API documentation for options and response details.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
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 request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use screenshot tools. 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. Sign up for free and get 1,000 screenshots a month with no card.
Frequently asked questions
Does setWindowBounds resize the website viewport?
It sets browser-window bounds. Page viewport behavior is controlled separately; measure the page viewport when that is the requirement.
How do I find the window ID?
Call await page.windowId() on the page associated with the window, then pass that ID to the browser bounds methods.
Can Puppeteer set a browser window to fullscreen?
The official window-management guide demonstrates maximizing and restoring a window, and its fullscreen example uses the fullscreen window state. Check the current guide for your Puppeteer version and environment support.
Which approach should I use for screenshot testing?
Use setViewport for responsive layout screenshots. Use setWindowBounds only when the outer desktop window position or size is part of what you need to control.


