October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
browser automation

How to Download and Upload Files in Puppeteer (with Reliable Completion Checks)

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

Use a real file input for uploads, and configure a writable download directory for browser downloads. Puppeteer’s uploadFile() sends local paths to an <input type="file">. If a site opens a native chooser, arm page.waitForFileChooser() before clicking. Downloads need a browser-context policy, an explicit directory, and your own completion and integrity checks: Puppeteer’s maintained files guide says it currently has no universal programmatic file-download API.

Prerequisites and version caveats

Install the package that matches your browser strategy. puppeteer downloads a compatible Chrome by default; puppeteer-core is for a browser that you manage separately. Puppeteer controls Chrome or Firefox through DevTools Protocol or WebDriver BiDi, and download behavior can differ by installed version and protocol. Check the API reference for your exact version before deploying.

npm i puppeteer

The examples below use modern Puppeteer syntax and absolute paths. Run them on the machine where Puppeteer runs; a path on your laptop is not automatically visible to a remote browser process.

Upload a file through an HTML input

Direct upload with uploadFile

Most upload forms contain an input such as <input type="file">. Wait for it, then provide one or more absolute local paths.

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();
  const page = await browser.newPage();
  await page.goto('https://example.com/upload', {waitUntil: 'networkidle2'});

  const fileInput = await page.waitForSelector('input[type="file"]');
  await fileInput.uploadFile('/absolute/path/to/report.pdf');

  // Selecting a file only changes the browser input. It does not submit it.
  await Promise.all([
    page.waitForResponse(r => r.url().includes('/upload') && r.ok()),
    page.locator('button[type="submit"]').click(),
  ]);

  await browser.close();
})();

For a multiple-file input, pass multiple paths and leave the page’s validation attributes unchanged:

await fileInput.uploadFile(
  '/absolute/path/to/one.csv',
  '/absolute/path/to/two.csv'
);

Use a selector specific to your form when several file inputs exist. Confirm acceptance by waiting for the documented response, navigation, or success indicator; a changed filename label is not proof that the server received bytes.

When the page opens a native file chooser

A custom “Choose file” button may launch the operating-system dialog instead of exposing a convenient input. Register the chooser wait before the click so the event cannot be missed.

const [chooser] = await Promise.all([
  page.waitForFileChooser({timeout: 5000}),
  page.locator('#choose-file').click(),
]);
await chooser.accept(['/absolute/path/to/report.pdf']);

The same ordering rule applies to navigation and responses: arm the wait and perform the triggering action in one Promise.all. If the control does not launch a chooser in your browser mode, inspect the page for its underlying file input and use uploadFile instead.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Submit and verify the upload

Choose the synchronization signal that represents success for the application:

  • Response: wait for the upload endpoint and require response.ok().
  • Navigation: wait for the destination URL and a suitable load state.
  • UI state: wait for a success element, job ID, or server-rendered status.
const [response] = await Promise.all([
  page.waitForResponse(r => r.url().includes('/upload') && r.request().method() === 'POST'),
  page.locator('button[type="submit"]').click(),
]);
if (!response.ok()) throw new Error(`Upload failed: HTTP ${response.status()}`);
await page.waitForSelector('[data-upload-status="success"]');

Configure a controlled browser download

Set policy and an explicit destination

For a browser-managed download, create a browser context with a writable, per-job directory. The downloadPath is required when policy is allow or allowAndName.

const fs = require('node:fs/promises');
const path = require('node:path');
const puppeteer = require('puppeteer');

(async () => {
  const downloadPath = path.resolve('downloads/job-001');
  await fs.rm(downloadPath, {recursive: true, force: true});
  await fs.mkdir(downloadPath, {recursive: true});

  const browser = await puppeteer.launch();
  const context = await browser.createBrowserContext({
    downloadBehavior: {
      policy: 'allow',
      downloadPath,
    },
  });
  const page = await context.newPage();
  await page.goto('https://example.com/reports', {waitUntil: 'networkidle2'});
  await page.locator('a[data-download="report"]').click();
  await browser.close();
})();

allowAndName is another policy; it names files by download GUID and has a WebDriver BiDi limitation documented in the DownloadBehavior API. Treat the directory as owned by one job to avoid collisions with stale files or another worker.

How to know a download finished

Download configuration alone is not completion proof. Puppeteer’s maintained files guide states: “Currently, Puppeteer does not offer a way to handle file downloads in a programmatic way.” Build a bounded completion check using protocol notifications where your chosen browser/protocol exposes them, or poll the job directory and then validate the result.

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

A defensive filesystem poll

Start with an empty directory, record its initial contents, ignore temporary .crdownload files, require a deadline, and verify more than a nonzero length. The example below waits for a stable size and then checks a minimum expected size; production code should also validate a checksum or parse the file format.

const fs = require('node:fs/promises');
const path = require('node:path');

async function waitForCompletedFile(dir, {
  timeoutMs = 60_000,
  intervalMs = 250,
  minBytes = 1,
} = {}) {
  const deadline = Date.now() + timeoutMs;
  let previous = null;
  while (Date.now() < deadline) {
    const names = await fs.readdir(dir);
    const candidates = names.filter(name => !name.endsWith('.crdownload'));
    for (const name of candidates) {
      const full = path.join(dir, name);
      const first = await fs.stat(full);
      if (!first.isFile() || first.size < minBytes) continue;
      await new Promise(r => setTimeout(r, intervalMs));
      const second = await fs.stat(full);
      const state = `${name}:${second.size}`;
      if (second.size === first.size && state === previous) return full;
      previous = state;
    }
    await new Promise(r => setTimeout(r, intervalMs));
  }
  throw new Error(`Download did not complete within ${timeoutMs} ms`);
}

const file = await waitForCompletedFile('/absolute/path/to/job-directory', {
  timeoutMs: 90_000,
  minBytes: 100,
});
console.log(`Candidate completed: ${file}`);

A stable size can still be the wrong file or a truncated response. Prefer an expected filename, a download notification, a known byte count, a cryptographic checksum, a valid archive/PDF parse, or an application-level job status. Keep the timeout finite so a blocked server cannot consume a worker indefinitely.

Use a direct HTTP request when the URL is suitable

If the download URL is stable and your authorization permits it, an HTTP request is often simpler than browser automation. Preserve only the required origin-scoped cookies or tokens, check status and content type, stream large responses with size limits, and write into the job directory. Keep browser interaction when authentication, navigation, a user gesture, or a JavaScript-generated URL is part of the requirement.

Upload versus download: choose the right synchronization

Task Primary mechanism What proves success Main risk
Upload input uploadFile Upload response, navigation, or success state Selection mistaken for submission
Native chooser waitForFileChooser before click Application acknowledgement after accept Wait registered too late
Browser download Context policy plus writable downloadPath Notification or bounded filesystem and integrity checks Stale, partial, or incorrectly named file
Direct HTTP Authorized request to stable URL HTTP status, headers, size and checksum/parser Missing browser cookies, gesture, or generated authorization

Troubleshooting Puppeteer file transfers

“File not found” or permission errors

  • Resolve the path on the machine running Node, not the machine displaying your terminal.
  • Use an absolute path and verify read permission for uploads and write permission for the download directory.
  • Create a unique per-job directory and ensure the process user owns it.

The chooser wait times out

The click may not open a native chooser, or the wait was registered after the click. Put both operations in the same Promise.all; otherwise inspect the DOM for an input[type=file].

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

The upload appears selected but the server has nothing

Selection does not submit data. Click the site’s submit control and wait for its response, navigation, or success state. Check client-side validation and required fields.

No file appears in the download directory

Confirm the context was created with policy: 'allow' or 'allowAndName', that downloadPath is absolute and writable, and that the click actually triggers a download rather than navigation or an in-page viewer. Check authentication and pop-up blocking as well.

The file is present but incomplete

Do not treat existence or one nonzero size as completion. Ignore .crdownload, wait for size stability within a deadline, and validate expected bytes, checksum, or file parsing. Remove stale files before each job.

Code works in Chrome but not Firefox or BiDi

Download policy and notification support vary by browser and protocol. Consult the installed version’s API reference, especially the DownloadBehavior notes about allowAndName and WebDriver BiDi.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
The SQL Programming Language: .
  • Used Book in Good Condition
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Operational checklist

  1. Use absolute local paths and verify permissions.
  2. Use uploadFile for exposed file inputs; use chooser interception only when needed.
  3. Arm response, navigation, or chooser waits before the triggering action.
  4. Submit the form and verify an application-level success signal.
  5. For downloads, create an empty per-job directory and configure policy plus downloadPath.
  6. Use bounded polling or protocol events; reject temporary and stale files.
  7. Validate filename, size, bytes, checksum, parser result, or downstream state.
  8. Clean up job directories according to your retention policy.

Or skip the browser setup

If your goal is a clean image or PDF of a page rather than automating that site’s own file control, ScreenshotNeo provides a one-request website screenshot API. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for all options, including PNG, JPEG, WebP, PDF, full-page capture, selectors, waits, custom headers and cookies, and signed links. Create a free ScreenshotNeo account to get 1,000 screenshots each month without a card.

FAQ

Where does Puppeteer save downloads by default?

Do not rely on an implicit location. Set a writable downloadPath in the browser context and treat that directory as the job’s destination.

Can I upload a remote URL with uploadFile?

No. The method reads paths local to the process running Puppeteer. Download the remote data first, enforce your own size and type checks, then pass the resulting local path.

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.

Is a nonzero file size enough?

No. It can be a stale or truncated file. Combine bounded waiting with expected naming and an integrity or application-level validation.

Frequently Asked Questions

Does Puppeteer provide a universal download event API?

The maintained files guide does not document a universal programmatic download API, so configure download behavior and add your own bounded completion and integrity checks.

Should I use a browser download or direct HTTP?

Use direct HTTP for a stable, authorized URL when browser interaction is unnecessary; keep browser automation when authentication, navigation, JavaScript generation, or a user gesture is required.

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.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.