Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To launch a visible Chrome window and request a 1,200 × 800 page content area, use headless: false, remove Puppeteer’s default viewport, and call the experimental page.resize() API:

const browser = await puppeteer.launch({ headless: false });
const page = (await browser.pages())[0];

await page.setViewport(null);
await page.resize({ contentWidth: 1200, contentHeight: 800 });

This sets the content area, not necessarily the outside dimensions of the native window. If you need outer bounds or a maximized window, use page.windowId() with browser.setWindowBounds(). Verify the result in the running page because browser chrome, display limits, the operating system and the window manager affect the final size.

Choose what “window size” means

Puppeteer exposes three related but different sizing controls. Choosing the wrong one is the most common reason a headful window appears to have the “wrong” dimensions.

Goal API What the dimensions represent Important limitation
Set an emulated page viewport page.setViewport({width, height}) CSS-pixel viewport available to the page It does not directly guarantee the native window’s outside size; some viewport changes can reload the page.
Set visible content area page.setViewport(null), then page.resize({contentWidth, contentHeight}) Page content area, excluding browser UI Page.resize is marked experimental and must be checked against your installed Puppeteer version.
Set native window bounds or state page.windowId(), then browser.setWindowBounds(id, bounds) Browser window bounds or state Display boundaries and platform window rules can still constrain the usable result.

page.viewport() reports Puppeteer’s configured viewport settings. It does not inspect the actual native page viewport, so it is not sufficient proof that a visible window reached the requested dimensions.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set a precise headful content area

Complete runnable example

Install Puppeteer in a Node.js project, then run this script. It opens a visible browser, obtains the first page, removes the default viewport constraint, requests a 1,200 × 800 content area, and waits for the browser’s resize event before reading the measured values.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = (await browser.pages())[0];

  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

  // Let the native headful window determine the viewport.
  await page.setViewport(null);

  const resizeEvent = page.evaluate(() => new Promise(resolve => {
    window.addEventListener('resize', () => resolve({
      innerWidth: window.innerWidth,
      innerHeight: window.innerHeight,
      outerWidth: window.outerWidth,
      outerHeight: window.outerHeight
    }), { once: true });
  }));

  if (typeof page.resize !== 'function') {
    throw new Error('This Puppeteer version does not expose page.resize().');
  }

  await page.resize({ contentWidth: 1200, contentHeight: 800 });
  const measured = await Promise.race([
    resizeEvent,
    new Promise((_, reject) => setTimeout(() => reject(new Error('Resize event timed out')), 10000))
  ]);

  console.log(measured);
  // Keep the window open for inspection. Close it when your work is complete.
  // await browser.close();
})();

The resize event is registered before the request because the inner dimensions update asynchronously. The measured innerWidth and innerHeight describe the page’s usable content area. outerWidth and outerHeight include browser chrome and therefore will normally differ.

Use your own dimensions

Replace contentWidth and contentHeight with the target CSS-pixel dimensions. For example, page.resize({contentWidth: 1440, contentHeight: 900}) requests a 1,440 × 900 content area. The request is not a promise that the host can provide that size: a small display, tiling window manager, remote desktop session or operating-system policy may reduce it.

Set the outer window bounds or maximize it

Use the bounds API when your requirement concerns the native window rectangle rather than the page content. Get the window identifier from the page and pass bounds or a window state to the browser:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = (await browser.pages())[0];
  const windowId = await page.windowId();

  await browser.setWindowBounds(windowId, {
    width: 1280,
    height: 900
  });

  // For a maximized visible window instead:
  // await browser.setWindowBounds(windowId, { windowState: 'maximized' });

  console.log(await page.evaluate(() => ({
    innerWidth: window.innerWidth,
    innerHeight: window.innerHeight,
    outerWidth: window.outerWidth,
    outerHeight: window.outerHeight
  })));
})();

Outer bounds include title bars, toolbars and other browser UI. Consequently, setting a 1,280 × 900 outer rectangle will not produce a 1,280 × 900 web-content area. If the content dimensions are the acceptance criterion, use page.resize and verify innerWidth/innerHeight.

Viewport sizing versus a real visible window

page.setViewport()

page.setViewport({width: 1200, height: 800}) is appropriate when responsive-layout testing is the goal. It sets the page’s CSS viewport and can be combined with device scale, mobile and touch emulation. It is not a reliable way to measure or control the native headful window’s outside dimensions.

page.setViewport(null)

In a headful window-sizing workflow, null removes Puppeteer’s configured viewport constraint. Without this step, the requested native content size can be constrained by the default viewport emulation, producing confusing measurements.

page.resize()

The current Page API labels resize experimental. Its support and behavior can differ between the Puppeteer package, Chrome build, operating system, display configuration and window manager you have installed. Pin and test the dependency used by your project rather than assuming every version behaves identically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Verify the dimensions instead of trusting configuration

Use browser-side measurements after the resize event:

const size = await page.evaluate(() => ({
  innerWidth: window.innerWidth,
  innerHeight: window.innerHeight,
  outerWidth: window.outerWidth,
  outerHeight: window.outerHeight,
  devicePixelRatio: window.devicePixelRatio
}));
console.log(size);
  • innerWidth/innerHeight: the content area in CSS pixels.
  • outerWidth/outerHeight: the visible browser window including UI.
  • devicePixelRatio: the relationship between CSS pixels and physical display pixels; it does not change the CSS dimensions you requested.

The official window-management example reports an inner size of 600 × 400 and an outer size of 600 × 487. Those are documentation example values, not universal title-bar measurements. Your platform will produce different chrome and borders.

Headful screen and command-line caveats

Do not use headless-only screen assumptions to size a visible window. The official screen-configuration guidance says --screen-info is available only in headless mode, while headful Chrome uses the physical platform screens. Likewise, headless discussions of --window-size should not be treated as a guarantee for a visible Chrome window.

In a container, CI runner or remote desktop, there may be no normal physical display. A virtual display can make headful Chrome launch, but the available bounds still come from that host environment. Record the Puppeteer version, Chrome build, operating system and display setup when diagnosing inconsistent dimensions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common failures and fixes

“page.resize is not a function”

Your installed Puppeteer version does not expose the experimental API, or the object is not the Page instance you expect. Check typeof page.resize, confirm the locked package version, and upgrade or pin a version that supports the method. If changing dependencies is not possible, use setWindowBounds for native bounds or setViewport for CSS viewport emulation.

The browser is still headless

Launch with {headless: false}. Also check that a CI environment is not replacing your launch options and that Chrome is allowed to connect to a display.

The page remains at the old size

Call await page.setViewport(null) before page.resize. Register the resize listener before requesting the change, then read dimensions after the event. A fixed timeout alone can race the asynchronous update.

Outer dimensions do not equal the requested content dimensions

This is expected: browser UI occupies part of the outer rectangle. Decide whether your requirement is content or native bounds, then use the corresponding API and measurement.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The requested size is clipped or ignored

Inspect the host display and window manager. A maximized state, screen edge, remote session, desktop scaling policy or operating-system restriction can override the requested bounds. Verify actual values in the page and treat them as the runtime result.

A viewport change unexpectedly reloads the page

Some viewport settings can trigger a reload. Set the viewport before navigation when possible, or wait for navigation and application state again after changing it. For native content sizing, prefer the setViewport(null) plus resize sequence.

Practical reliability checklist

  1. Pin the Puppeteer dependency and record its version.
  2. Launch with headless: false and ensure a usable display exists.
  3. Choose content sizing, viewport emulation or outer bounds before writing code.
  4. For content sizing, call setViewport(null) before resize.
  5. Check that page.resize exists because it is experimental.
  6. Listen for the browser’s resize event before issuing the request.
  7. Validate innerWidth, innerHeight, outerWidth and outerHeight.
  8. Add a timeout and a recovery path for automation that cannot wait indefinitely.
  9. Test on the actual OS, display and window manager used in production.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your actual goal is a website screenshot rather than interactive browser control, ScreenshotNeo provides a single API request and an MCP server for AI agents. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.

Here is the same idea as a direct request (see the ScreenshotNeo API documentation for options):

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

ScreenshotNeo also supports custom content sizing, device presets, retina scale, full-page lazy-image loading, CSS-selector element capture, dark mode, PDFs, custom CSS and JavaScript, clicks, waits, blocked resources, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage reporting and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which can simplify migration.

AI workflows can call its MCP tools take_screenshot, get_page_info and capture_pdf from Claude, Cursor or another MCP client. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for the free plan.

Frequently Asked Questions

Can I use --window-size to guarantee a headful Chrome size?

No. Headless command-line sizing behavior does not guarantee the dimensions of a visible native window. Use the headful APIs and verify runtime measurements.

What does Puppeteer’s page.viewport() tell me?

It reports Puppeteer’s configured viewport settings, not a measurement of the actual native page or window. Use browser-side innerWidth and innerHeight after resizing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I close the browser immediately after resizing?

Only when the capture or test is complete. Keep it open while inspecting the result, and call browser.close() in production cleanup code.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.