ScreenshotNeo

BlogHow-to

How to Allow Multiple File Downloads in Puppeteer

Set a browser context download policy and destination to allow repeated downloads in Puppeteer. Learn the limits, filename behavior, and troubleshooting steps.

By the ScreenshotNeo team30 September 20269 min read

How to Allow Multiple File Downloads in Puppeteer

To allow a page to download multiple files in Puppeteer, configure the browser context with a downloadBehavior policy of allow and an absolute, writable downloadPath. Then trigger the site’s download actions from a page in that context. This lets Chrome save repeated downloads to the specified directory; it does not give Puppeteer a supported download-completion event or file-management API.

const context = await browser.createBrowserContext({
  downloadBehavior: {
    policy: 'allow',
    downloadPath: '/absolute/path/to/downloads',
  },
});
const page = await context.newPage();

The destination must exist or be creatable by your application, and the process running Chrome must have permission to write there. If you need to wait for each download to finish, inspect it, or process it, plan for a separate filesystem or browser-protocol strategy and validate that strategy against your Puppeteer and Chrome versions. The documented setting controls browser saving; it does not promise lifecycle handling.

1. What the download setting does

Puppeteer’s current browser context options document a downloadBehavior setting. Its policy can be allow, allowAndName, deny, or default. The downloadPath is the default directory for downloads and is required when the policy is allow or allowAndName. With allowAndName, Chrome names files using download GUIDs rather than preserving their usual names. See the Puppeteer BrowserContextOptions API.

In this context, “multiple” means that the browser is allowed to save more than one download. You still need to make the page initiate each download. A site might start a file download after a button click, after submitting a form, or after an authenticated request. There is no universal Puppeteer action that initiates downloads on every website.

Keep three separate tasks in mind:

  • Permission: the context policy allows downloads.
  • Initiation: your script performs the site-specific action that causes a download.
  • Completion and handling: your application determines that a file is complete and processes it, if required.

The documented setting covers permission and the default save directory. It does not, by itself, implement the third task.

2. Configure a context and save multiple files

Use a dedicated browser context when you want a separate download policy and page state. The following CommonJS example creates a directory, enables downloads, and clicks two example links. Replace the example URL and selectors with the real site’s behavior.

The context policy allows saving to a destination; the page still has to initiate each download.
The context policy allows saving to a destination; the page still has to initiate each download.
const fs = require('node:fs/promises');
const path = require('node:path');
const puppeteer = require('puppeteer');

async function main() {
  const downloadPath = path.resolve(process.cwd(), 'downloads');
  await fs.mkdir(downloadPath, { recursive: true });

  const browser = await puppeteer.launch({ headless: true });
  try {
    const context = await browser.createBrowserContext({
      downloadBehavior: {
        policy: 'allow',
        downloadPath,
      },
    });
    const page = await context.newPage();

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

    // Example only: use selectors that match the target site.
    await page.locator('a[data-report="first"]').click();
    await page.locator('a[data-report="second"]').click();

    // This code demonstrates triggering both actions. It does not
    // establish a supported Puppeteer download-completion signal.
    console.log(`Downloads are directed to ${downloadPath}`);
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run it with Node.js after installing Puppeteer in your project. The Puppeteer package and browser version should be compatible; consult the official installation and browser support documentation. Puppeteer v20 onward uses Chrome for Testing, and its supported-browser guidance describes headless and headful modes sharing the same code path. Version matching matters because API surfaces and Chrome behavior can change.

Choose a policy based on filenames

Policy Effect When to choose it
allow Allows downloads to the configured default directory. Use when you want the browser’s regular filenames and a known save location.
allowAndName Allows downloads and names them with download GUIDs. Use only if GUID-based names suit your workflow; do not expect the original names.
deny Blocks downloads. Use when a context must not save files.
default Uses the browser’s default download behavior. Use when you do not need the explicit allow behavior. Do not assume this is equivalent to an explicit allow policy.

When a script or downstream job expects files such as report.csv, prefer allow and verify the site’s naming behavior. With allowAndName, names are GUID-based, so a later step cannot safely assume the original filename. The official API reference describes the allowed policy values and path requirement.

3. Trigger downloads the way the page expects

Once the context is configured, use the interaction the site actually requires. For a direct link, click the link; for a generated export, fill the form and submit it; for a menu action, open the menu and choose the export. A successful click only means the interaction occurred. It does not prove the browser saved a complete file.

Sequential actions

Start with sequential clicks when downloads depend on changing page state, a menu, or a server-side export job. This keeps the interaction order clear and can make site-specific failures easier to diagnose. It does not guarantee that the first file finished before the second began.

await page.locator('[data-action="export-csv"]').click();
await page.locator('[data-action="export-pdf"]').click();

Parallel actions

Only trigger several actions together if the site supports concurrent exports and each action is independent. Parallel browser interactions can race over shared page state, trigger rate limits, or produce confusing outcomes. Allowing downloads does not make the site’s export endpoint concurrent-safe.

Authenticated and generated files

If a download depends on login state, perform the login in the same context that will open the export page. If the site creates a file asynchronously, wait for the site’s own “ready” state before clicking its download control. The context option does not add authentication, wait for a background export, or change which URL the page requests.

4. Know the programmatic handling limit

Puppeteer’s official Files guide states: “Currently, Puppeteer does not offer a way to handle file downloads in a programmatic way.” This is the key limitation for workflows that need reliable per-file completion handling.

Choose the policy with filename expectations in mind: allow preserves regular names, while allowAndName uses GUIDs.
Choose the policy with filename expectations in mind: allow preserves regular names, while allowAndName uses GUIDs.

Do not treat the click promise as a download promise. Do not assume that the context setting provides a download event, progress report, completion promise, or supported API for opening and managing downloaded files. If your application needs to verify completed files, consider a separately validated strategy such as observing the configured directory or using browser protocol capabilities. Those approaches require their own version checks, handling for temporary or partially written files, and tests against the target site; they are not supplied by the documented setting.

For production jobs, write the intended directory into job configuration and keep it isolated per job or worker. If multiple browser processes share a directory, filenames can collide or be hard to associate with the correct task. A unique directory per job makes later inspection easier, but your application still needs a reliable way to establish file completion before consuming a file.

5. Path, permissions, and execution environment

  • Use an absolute path. Resolve relative paths in your code so the destination does not vary with the process working directory.
  • Create the directory before launching the download. Recursive directory creation avoids a missing-folder failure.
  • Check the Chrome process identity. In a container or remote browser setup, the browser’s filesystem may differ from the Node process filesystem. The path must be valid where Chrome writes.
  • Check write permissions and disk space. A valid path can still fail if the browser user cannot write there or the volume is full.
  • Keep jobs separated. Use a per-job folder if multiple jobs can save files concurrently.
  • Clean up deliberately. Remove old job folders only after your application no longer needs their files.

When connecting to a browser instead of launching one locally, verify the relevant options against the installed Puppeteer release and the browser connection configuration. The current API references include context and connection options, but supported behavior is version-dependent.

6. Troubleshooting common failures

Symptom Likely cause What to check
No file appears The policy is not allow or allowAndName, or the site action did not initiate a download. Confirm the context options and verify the click, export state, and site response.
Context setup rejects the options The installed Puppeteer version may not expose the documented option in the way your code expects. Check the API reference for your installed release and update code to match it.
Browser reports a path error The path is missing, relative in an unexpected working directory, or unusable by the Chrome process. Resolve it to an absolute path, create the directory, and check permissions in the browser environment.
Files have unexpected names The policy is allowAndName, which uses GUID-based names. Use allow if the regular download filenames are important.
Second export fails or replaces state The page or server may not support simultaneous exports, or actions are racing. Try sequential actions and wait for the site’s own export-ready state between them.
Script proceeds while a file is incomplete A click resolved, but the documented API did not provide a completion signal. Do not use click completion as file completion. Add a separately validated filesystem or protocol strategy.
Works locally but not in CI The CI browser may run under another user, use another filesystem, or pair with a different supported browser. Check path visibility, write access, available disk space, and Puppeteer/browser versions.

7. Performance, reliability, and cost

Download configuration itself does not make exports faster. The site’s generation time, file size, network, and browser environment determine how long a download takes. Triggering many exports at once can increase server load and memory or disk pressure. Start with sequential exports when correctness matters, then increase concurrency only after confirming the site and your worker design support it.

For reliability, give each job a predictable destination and record which export actions were requested. Keep browser and Puppeteer versions pinned in deployment, and check compatibility when upgrading. The supported-browser pairing can change over time; Puppeteer’s support guide says v20 onward downloads and works with Chrome for Testing. Headless and headful modes share the same code path according to that guide, but site behavior and environment still need validation.

There is no Puppeteer download fee described by the cited API documentation. Operational costs may include browser compute, network transfer, and storage in your own environment. Avoid retaining files longer than needed, and set practical limits for file sizes and job concurrency in your application.

8. Or skip the browser setup

If the task is capturing a webpage rather than saving files from it, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request 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}`);

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its 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 free and get 1,000 screenshots a month with no card.

9. FAQ

Can Puppeteer click several download buttons?

Yes. Your script can perform multiple page interactions. The context policy allows the browser to save downloads, while the site determines how each button behaves. The documented configuration does not report completion for each file.

Should I use allow or allowAndName?

Use allow when you want the browser’s regular download names. Choose allowAndName only when GUID-based filenames work for your downstream process.

Does downloadBehavior tell me when every file is finished?

No. The Puppeteer Files guide says the library does not currently offer a way to handle downloads programmatically. Treat completion handling as a separate requirement.

Does this apply to every Puppeteer and Chrome version?

Check the documentation for the versions you install. Puppeteer’s browser pairing and API surface are version-specific and can change.