Puppeteer Browser API: How to Manage Browser Downloads
Configure Puppeteer’s browser download policy and path, then learn what Puppeteer does—and does not—provide for tracking downloaded files.
Puppeteer can configure whether a browser context allows downloads and where allowed downloads are saved. The documented policies are deny, allow, allowAndName, and default. For allow or allowAndName, provide a writable downloadPath. Puppeteer’s Files guide says it currently does not provide a programmatic API for handling downloaded files: setting the policy does not give your script a downloaded-file object or a built-in completion callback.
This guide covers page-triggered downloads separately from installing Puppeteer’s browser binary. The API references cited here identify Puppeteer 25.12.0; check your installed version and browser/protocol before adopting the configuration.
1. Configure downloads for a browser context
Choose a policy, create the destination directory, and pass the behavior through the context options. The path must be writable by the process running the browser.
import puppeteer from 'puppeteer';
import { mkdir } from 'node:fs/promises';
import path from 'node:path';
const downloadPath = path.resolve('downloads');
await mkdir(downloadPath, { recursive: true });
const browser = await puppeteer.launch();
const context = await browser.createBrowserContext({
downloadBehavior: {
policy: 'allow',
downloadPath,
},
});
try {
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Trigger the site's download using its normal UI or page behavior.
// Puppeteer does not provide a built-in downloaded-file completion API.
} finally {
await context.close();
await browser.close();
}
createBrowserContext({ downloadBehavior }) is the context-options form. The documented field is optional; when omitted, the browser’s default behavior applies. Ensure the directory exists and is writable before triggering the download.
2. Choose a download policy
| Policy | Effect | Path requirement |
|---|---|---|
deny |
Disallow downloads for the context. | No path requirement stated for this policy. |
allow |
Allow browser downloads using normal browser naming behavior. | downloadPath is required. |
allowAndName |
Allow downloads and name files according to their download GUIDs. | downloadPath is required. |
default |
Leave behavior to the browser default. | No path requirement stated for this policy. |
Use allow when you want the browser’s ordinary filename behavior. Choose allowAndName only if GUID-based names suit your workflow; do not expect the original suggested filename. Use deny when a workflow must not save downloads. Omitting downloadBehavior also leaves behavior to the browser default.
3. Configure behavior while connecting
If the automation connects to an existing browser, the documented ConnectOptions also has a downloadBehavior option. The exact behavior depends on the browser and connection setup, so confirm it against your installed Puppeteer version and target browser.
import puppeteer from 'puppeteer';
import { mkdir } from 'node:fs/promises';
import path from 'node:path';
const browserURL = 'http://127.0.0.1:9222';
const downloadPath = path.resolve('downloads');
await mkdir(downloadPath, { recursive: true });
const browser = await puppeteer.connect({
browserURL,
downloadBehavior: {
policy: 'allow',
downloadPath,
},
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Trigger the download through the page as required by your workflow.
} finally {
await browser.disconnect();
}
disconnect() detaches Puppeteer from the browser; it does not close the browser process. The snippet configures behavior through the documented connect option, but does not add a completion event or file-management API.
4. Wait for and verify a downloaded file
Puppeteer’s Files guide says it does not currently offer programmatic file-download handling. In particular, do not treat downloadBehavior as a Puppeteer promise that resolves with the finished path. If your browser and runtime setup support filesystem-based observation, you can implement a separate environment-level check. This is an implementation pattern using Node.js filesystem facilities, not a built-in Puppeteer download API.
- Use a dedicated, initially empty download directory for each run where practical.
- Trigger the download after configuring the context.
- Observe the directory using your runtime’s filesystem facilities, with a finite timeout.
- Check that the expected file exists, has nonzero size, and can be parsed as the expected format.
- Account for temporary or partial files according to the browser and operating system used; the cited Puppeteer references do not define a universal naming or completion convention.
A simple polling helper can be adapted for an environment where the browser writes the completed file directly under the expected name. It detects a stable file size over consecutive checks; it does not establish that the file is semantically valid, and it may need adaptation for temporary filenames or browser-specific behavior.
import { readdir, stat } from 'node:fs/promises';
import path from 'node:path';
async function waitForStableFile(directory, expectedName, {
timeoutMs = 30_000,
intervalMs = 250,
stableChecks = 3,
} = {}) {
const target = path.join(directory, expectedName);
const deadline = Date.now() + timeoutMs;
let lastSize = -1;
let stableCount = 0;
while (Date.now() < deadline) {
const names = await readdir(directory);
if (names.includes(expectedName)) {
try {
const info = await stat(target);
if (info.isFile() && info.size > 0 && info.size === lastSize) {
stableCount += 1;
if (stableCount >= stableChecks) return target;
} else {
stableCount = 0;
}
lastSize = info.size;
} catch (error) {
if (error.code !== 'ENOENT') throw error;
lastSize = -1;
stableCount = 0;
}
}
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
throw new Error(`Timed out waiting for a stable file: ${target}`);
}
// Example after triggering the download:
// const savedPath = await waitForStableFile(downloadPath, 'report.csv');
This example assumes the expected filename is known and appears in the directory. If the server supplies a changing filename or the browser uses GUID filenames, first determine the output naming behavior for your selected policy and setup. Stable size is only a practical signal; validate file contents when correctness matters.
5. Common problems and fixes
| Symptom | Likely cause | What to check |
|---|---|---|
| Download does not start | The policy denies it, defaults differ from your expectation, or the page never triggered a download. | Set an explicit policy, confirm the triggering action and inspect the browser’s own behavior. |
| Browser rejects or ignores the destination | downloadPath is missing for allow/allowAndName, or the path is not writable. |
Supply an absolute path, create the directory, and check process permissions. |
| Saved name is not the expected name | allowAndName uses download GUIDs. |
Use allow if normal browser naming is needed, or adapt downstream processing to the GUID-based name. |
| Automation waits forever | The script assumes a Puppeteer completion callback or watches for the wrong filename. | Use a bounded timeout and a runtime-level filesystem check suited to the browser’s naming behavior. |
| File exists but is incomplete or invalid | Existence alone does not prove completion or valid content. | Check stability as a heuristic, then validate the file format or parse its contents. |
| Configuration works in one setup but not another | Puppeteer version, browser, or protocol differs. | Check the installed version and the relevant browser/context or connect configuration against current official references. |
6. Keep page downloads separate from Puppeteer installation
The browser binary used to run automation is a different download from a file the page saves. The puppeteer package downloads a compatible Chrome for Testing binary during installation. Since Puppeteer 19.0.0, its documented default cache location is $HOME/.cache/puppeteer. By contrast, puppeteer-core does not download Chrome; it is intended for setups where you manage the browser installation or connect to a remote browser.
If an installation problem is the issue, investigate the package install, cache location, and managed browser setup. Changing a page’s downloadBehavior does not configure Puppeteer’s browser installation.
7. Performance, reliability, and cost
A page-triggered download consumes time and storage in your own browser environment. Use a dedicated destination, set finite waits in your orchestration, and clean up generated files according to your application’s retention needs. The cited API references provide no performance benchmarks or cost figures, so capacity and cost depend on your runtime, browser hosting, storage, and workload.
For reliable workflows, make retries safe: avoid processing a file until your environment-level checks and format validation pass, and keep each run’s output isolated so an older file cannot be mistaken for a new one. Browser and protocol behavior can vary; verify the exact setup you deploy.
8. Or skip the browser setup
If your goal is a clean screenshot of a page rather than a downloaded document, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF; see the API documentation.
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, 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 take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month, with no card.
9. FAQ
Does Puppeteer support file downloads?
It can configure browser-context download behavior, but its Files guide says Puppeteer does not currently offer programmatic file-download handling.
Which policy gives files GUID-based names?
allowAndName.
Does puppeteer-core download Chrome?
No. The package does not download Chrome; browser installation or connection is managed by your setup.


