> ## Documentation Index
> Fetch the complete documentation index at: https://docs.browserbase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Downloads

> Triggering and retrieving downloaded files

Unlike screenshots saved locally, files downloaded during browser automation are stored in Browserbase's cloud storage. Retrieve them using the Browserbase API.

A common use case for headless browsers is downloading files from web pages. Browserbase syncs every downloaded file to cloud storage and appends a Unix timestamp to avoid naming conflicts (e.g., `sample.pdf` becomes `sample-1719265797164.pdf`).

<Callout icon="code" color="#6ec202" iconType="regular">View or run the example template [here](https://www.browserbase.com/templates/download-financial-statements)</Callout>

## Triggering downloads

First, trigger a download in your browser automation:

1. [Create a browser session](/platform/browser/getting-started/create-browser-session) and get the session ID
2. [Connect to the session](/platform/browser/getting-started/using-browser-session) using your preferred framework
3. Configure your library's downloads location
4. Perform the download action in your automation script

<Tabs>
  <Tab title="Node.js">
    <CodeGroup>
      ```typescript Playwright theme={null}
      import { chromium } from "playwright-core";
      import { Browserbase } from "@browserbasehq/sdk";

      (async () => {
        const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
        const session = await bb.sessions.create();

        const browser = await chromium.connectOverCDP(session.connectUrl);
        const defaultContext = browser.contexts()[0];
        const page = defaultContext.pages()[0];

        // Required to avoid playwright overriding location
        const client = await defaultContext.newCDPSession(page);
        await client.send("Browser.setDownloadBehavior", {
          behavior: "allow",
          downloadPath: "downloads",
          eventsEnabled: true,
        });

        await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");

        const [download] = await Promise.all([
          page.waitForEvent("download"),
          page.locator("#download").click(),
        ]);

        let downloadError = await download.failure();
        if (downloadError !== null) {
          console.log("Error happened on download:", downloadError);
          throw new Error(downloadError);
        }

        // Store the session ID to retrieve downloads later
        console.log("Download completed. Session ID:", session.id);

        await page.close();
        await browser.close();
      })().catch((error) => console.error(error.message));
      ```

      ```typescript Puppeteer theme={null}
      import puppeteer from "puppeteer-core";
      import { Browserbase } from "@browserbasehq/sdk";

      (async () => {
        const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
        const session = await bb.sessions.create();

        const browser = await puppeteer.connect({
          browserWSEndpoint: session.connectUrl,
        });
        const pages = await browser.pages();
        const page = pages[0];

        const client = await page.createCDPSession();
        await client.send("Browser.setDownloadBehavior", {
          behavior: "allow",
          downloadPath: "downloads",
          eventsEnabled: true,
        });

        await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");

        const [downloadPath] = await Promise.all([
          // Puppeteer doesn't have a download event, so we need to use the CDP session to listen for the download progress
          new Promise<string>(resolve => {
            client.on('Browser.downloadProgress', event => {
              if (event.state === 'completed') {
                resolve(event.guid);
              }
            });
          }),
          page.locator("#download").click(),
        ]);

        // Store the session ID to retrieve downloads later
        console.log("Download completed. Session ID:", session.id);

        await page.close();
        await browser.close();
      })().catch((error) => console.error(error.message));
      ```

      ```typescript Selenium theme={null}
      import http from "http";
      import { Builder, By } from "selenium-webdriver";
      import { Browserbase } from "@browserbasehq/sdk";

      (async () => {
        const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });
        const session = await bb.sessions.create();

        // Create a custom connection with the session details
        const customHttpAgent = new http.Agent({});
        (customHttpAgent as any).addRequest = (req: any, options: any) => {
          req.setHeader("x-bb-signing-key", session.signingKey);
          (http.Agent.prototype as any).addRequest.call(customHttpAgent, req, options);
        };

        const driver = new Builder()
          .forBrowser("chrome")
          .usingHttpAgent(customHttpAgent)
          .usingServer(
            session.seleniumRemoteUrl
          )
          .build();

        // Head to the test downloads page
        await driver.get("https://browser-tests-alpha.vercel.app/api/download-test");

        // Click the download button
        console.log("Downloading file to the remote browser...");
        await driver.findElement(By.id("download")).click();

        // Store the session ID to retrieve downloads later
        console.log("Download completed. Session ID:", session.id);

        // Clean up
        await driver.quit();
      })().catch((error) => console.error(error.message));
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Python">
    <CodeGroup>
      ```python Playwright theme={null}
      from playwright.sync_api import sync_playwright
      from browserbase import Browserbase
      import os

      bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
      session = bb.sessions.create()
      print(f"Session ID: {session.id}")

      with sync_playwright() as playwright: # Use the session ID to connect to the browser
          browser = playwright.chromium.connect_over_cdp(session.connect_url)

          # Required to avoid playwright overriding the location
          cdp_session = browser.new_browser_cdp_session()
          cdp_session.send(
              "Browser.setDownloadBehavior",
              {
                  "behavior": "allow",
                  "downloadPath": "downloads",
                  "eventsEnabled": True,
              },
          )

          context = browser.contexts[0]
          page = context.pages[0]

          # Head to the test downloads page
          page.goto("https://browser-tests-alpha.vercel.app/api/download-test")

          # Click the download button
          print("Downloading file to the remote browser...")
          page.get_by_role("link", name="Download File").click()

          # Clean up
          page.close()
          browser.close()

      print("Download completed. Use the session ID to retrieve the files.")

      ```

      ```python Selenium theme={null}
      from selenium import webdriver
      from selenium.webdriver.common.by import By
      from selenium.webdriver.remote.remote_connection import RemoteConnection
      from browserbase import Browserbase
      import os

      bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])
      session = bb.sessions.create()
      print(f"Session ID: {session.id}")

      # Create a custom connection with the session details
      class CustomRemoteConnection(RemoteConnection):
          def get_remote_connection_headers(self, parsed_url, keep_alive=False):
              headers = super().get_remote_connection_headers(parsed_url, keep_alive)
              headers["x-bb-api-key"] = os.environ["BROWSERBASE_API_KEY"]
              headers["session-id"] = session.id
              return headers

      custom_conn = CustomRemoteConnection(session.selenium_remote_url)
      driver = webdriver.Remote(
          command_executor=custom_conn,
          options=webdriver.ChromeOptions()
      )

      # Head to the test downloads page
      driver.get("https://browser-tests-alpha.vercel.app/api/download-test")

      # Click the download button
      print("Downloading file to the remote browser...")
      driver.find_element(By.ID, "download").click()

      # Clean up
      driver.quit()

      print("Download completed. Use the session ID to retrieve the files.")
      ```
    </CodeGroup>
  </Tab>
</Tabs>

<Warning>
  **Critical: `setDownloadBehavior` Configuration**

  When using Playwright or Puppeteer, you must call `Browser.setDownloadBehavior` via CDP to ensure downloads are synced to Browserbase's storage. Pay special attention to the `downloadPath` parameter—it must be set to `"downloads"` exactly as shown in the examples above.

  Common misconfiguration issues:

  * Using an absolute path (e.g., `/tmp/downloads`) instead of `"downloads"`
  * Omitting the `setDownloadBehavior` call entirely
  * Setting `behavior` to something other than `"allow"`

  Without proper configuration, your downloads won't be available for retrieval.
</Warning>

Opening a PDF URL in a browser session also triggers a download to Browserbase's cloud storage. To view the PDF instead of downloading it, configure your browser settings as shown [here](/platform/browser/files/screenshots#view-a-pdf-in-browser).

## Retrieving downloaded files

After triggering downloads, retrieve them using the [Downloads API](/reference/api/list-downloads). The API provides granular access to individual downloaded files — you can list, filter, retrieve, and delete downloads.

Filenames don't include the timestamp suffix Browserbase adds during storage.

<Note>
  Files sync in real time — large downloads may not be immediately available
  through the `/downloads` endpoint. The code below includes retry logic to
  handle this.
</Note>

### List downloads

List all downloads for a session with optional filtering by filename, MIME type, file size, and creation time.

<Tabs>
  <Tab title="Node.js">
    ```typescript Node theme={null}
    import { writeFileSync } from "node:fs";

    const API_KEY = process.env.BROWSERBASE_API_KEY!;

    async function saveDownloadsOnDisk(sessionId: string, retryForSeconds: number) {
      const endTime = Date.now() + retryForSeconds * 1000;

      while (Date.now() < endTime) {
        try {
          // List individual downloads for the session
          const listResponse = await fetch(
            `https://api.browserbase.com/v1/downloads?sessionId=${sessionId}`,
            { headers: { "x-bb-api-key": API_KEY } }
          );
          const { downloads, total } = await listResponse.json();

          if (total > 0) {
            console.log(`Found ${total} download(s)`);

            for (const download of downloads) {
              // Download each file individually
              const fileResponse = await fetch(
                `https://api.browserbase.com/v1/downloads/${download.id}`,
                {
                  headers: {
                    "x-bb-api-key": API_KEY,
                    Accept: "application/octet-stream",
                  },
                }
              );
              const buffer = Buffer.from(await fileResponse.arrayBuffer());
              writeFileSync(download.filename, buffer);
              console.log(`Saved: ${download.filename} (${download.size} bytes)`);
            }
            return;
          }
        } catch (e) {
          console.error("Error fetching downloads:", e);
          throw e;
        }

        // Wait 2 seconds before retrying
        await new Promise((resolve) => setTimeout(resolve, 2000));
      }

      throw new Error("No downloads found within the retry period");
    }

    (async () => {
      // Use the session ID from your browser automation to retrieve downloads
      const sessionId = "your-session-id";
      await saveDownloadsOnDisk(sessionId, 20); // wait up to 20s
      console.log("Downloads complete");
    })().catch((error) => {
      console.error("Download failed:", error);
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python Python theme={null}
    import os
    import time
    import requests

    API_KEY = os.environ["BROWSERBASE_API_KEY"]

    def save_downloads_on_disk(session_id: str, retry_seconds: int = 20):
        """
        List and download individual files from a session.
        Retries for the specified number of seconds if no downloads are found.

        :param session_id: The session ID from your browser automation
        :param retry_seconds: How long to retry if no downloads are found
        """
        end_time = time.time() + retry_seconds

        while time.time() < end_time:
            try:
                # List individual downloads for the session
                list_response = requests.get(
                    "https://api.browserbase.com/v1/downloads",
                    params={"sessionId": session_id},
                    headers={"x-bb-api-key": API_KEY},
                )
                data = list_response.json()

                if data["total"] > 0:
                    print(f"Found {data['total']} download(s)")

                    for download in data["downloads"]:
                        # Download each file individually
                        file_response = requests.get(
                            f"https://api.browserbase.com/v1/downloads/{download['id']}",
                            headers={
                                "x-bb-api-key": API_KEY,
                                "Accept": "application/octet-stream",
                            },
                        )
                        with open(download["filename"], "wb") as f:
                            f.write(file_response.content)
                        print(f"Saved: {download['filename']} ({download['size']} bytes)")
                    return
            except Exception as e:
                print(f"Error fetching downloads: {e}")
                raise

            time.sleep(2)  # Wait 2 seconds before retrying

        raise TimeoutError("No downloads found within the retry period")

    # Use the session ID from your browser automation to retrieve downloads
    session_id = "your-session-id"
    try:
        save_downloads_on_disk(session_id)
        print("Downloads complete")
    except Exception as e:
        print(f"Failed to retrieve downloads: {e}")
    ```
  </Tab>
</Tabs>

### Filtering options

| Parameter       | Type   | Description                                                |
| --------------- | ------ | ---------------------------------------------------------- |
| `sessionId`     | string | **Required.** The session ID to list downloads for.        |
| `filename`      | string | Filter by exact filename match.                            |
| `mimeType`      | string | Filter by MIME type (e.g., `application/pdf`).             |
| `minSize`       | number | Minimum file size in bytes.                                |
| `maxSize`       | number | Maximum file size in bytes.                                |
| `createdAfter`  | string | Filter downloads created after this timestamp (ISO 8601).  |
| `createdBefore` | string | Filter downloads created before this timestamp (ISO 8601). |
| `limit`         | number | Maximum results to return (1-100, default: 20).            |
| `offset`        | number | Number of results to skip for pagination.                  |

**Example with filters:**

<Tabs>
  <Tab title="Node.js">
    ```typescript Node theme={null}
    const API_KEY = process.env.BROWSERBASE_API_KEY!;
    const sessionId = "your-session-id";

    // Find PDF files larger than 1KB created in the last hour
    const params = new URLSearchParams({
      sessionId,
      mimeType: "application/pdf",
      minSize: "1024",
      createdAfter: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
      limit: "10",
    });

    const response = await fetch(
      `https://api.browserbase.com/v1/downloads?${params}`,
      {
        headers: { "x-bb-api-key": API_KEY },
      }
    );

    const { downloads } = await response.json();
    console.log(`Found ${downloads.length} matching PDFs`);
    ```
  </Tab>

  <Tab title="Python">
    ```python Python theme={null}
    import os
    from datetime import datetime, timedelta
    import requests

    API_KEY = os.environ["BROWSERBASE_API_KEY"]
    session_id = "your-session-id"

    # Find PDF files larger than 1KB created in the last hour
    response = requests.get(
        "https://api.browserbase.com/v1/downloads",
        params={
            "sessionId": session_id,
            "mimeType": "application/pdf",
            "minSize": 1024,
            "createdAfter": (datetime.now() - timedelta(hours=1)).isoformat(),
            "limit": 10,
        },
        headers={"x-bb-api-key": API_KEY}
    )

    downloads = response.json()["downloads"]
    print(f"Found {len(downloads)} matching PDFs")
    ```
  </Tab>
</Tabs>

### Get a download

Retrieve metadata or file content for a specific download. Use `Accept: application/json` for metadata, or `Accept: application/octet-stream` to download the file (default if no Accept header is provided).

<Tabs>
  <Tab title="Node.js">
    ```typescript Node theme={null}
    import { writeFileSync } from "node:fs";

    const API_KEY = process.env.BROWSERBASE_API_KEY!;
    const downloadId = "download-uuid";

    // Get metadata
    const metadataResponse = await fetch(
      `https://api.browserbase.com/v1/downloads/${downloadId}`,
      {
        headers: {
          "x-bb-api-key": API_KEY,
          Accept: "application/json",
        },
      }
    );
    const metadata = await metadataResponse.json();
    console.log(`File: ${metadata.filename} (${metadata.size} bytes)`);

    // Download file content
    const fileResponse = await fetch(
      `https://api.browserbase.com/v1/downloads/${downloadId}`,
      {
        headers: {
          "x-bb-api-key": API_KEY,
          Accept: "application/octet-stream",
        },
      }
    );
    const buffer = Buffer.from(await fileResponse.arrayBuffer());
    writeFileSync(metadata.filename, buffer);
    ```
  </Tab>

  <Tab title="Python">
    ```python Python theme={null}
    import os
    import requests

    API_KEY = os.environ["BROWSERBASE_API_KEY"]
    download_id = "download-uuid"

    # Get metadata
    metadata_response = requests.get(
        f"https://api.browserbase.com/v1/downloads/{download_id}",
        headers={
            "x-bb-api-key": API_KEY,
            "Accept": "application/json"
        }
    )
    metadata = metadata_response.json()
    print(f"File: {metadata['filename']} ({metadata['size']} bytes)")

    # Download file content
    file_response = requests.get(
        f"https://api.browserbase.com/v1/downloads/{download_id}",
        headers={
            "x-bb-api-key": API_KEY,
            "Accept": "application/octet-stream"
        }
    )
    with open(metadata["filename"], "wb") as f:
        f.write(file_response.content)
    ```
  </Tab>
</Tabs>

### Delete a download

Remove a download from storage. Returns `204 No Content` on success.

<Tabs>
  <Tab title="Node.js">
    ```typescript Node theme={null}
    const API_KEY = process.env.BROWSERBASE_API_KEY!;
    const downloadId = "download-uuid";

    const response = await fetch(
      `https://api.browserbase.com/v1/downloads/${downloadId}`,
      {
        method: "DELETE",
        headers: { "x-bb-api-key": API_KEY },
      }
    );

    if (response.status === 204) {
      console.log("Download deleted");
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python Python theme={null}
    import os
    import requests

    API_KEY = os.environ["BROWSERBASE_API_KEY"]
    download_id = "download-uuid"

    response = requests.delete(
        f"https://api.browserbase.com/v1/downloads/{download_id}",
        headers={"x-bb-api-key": API_KEY}
    )

    if response.status_code == 204:
        print("Download deleted")
    ```
  </Tab>
</Tabs>

### Download object

Each download contains the following fields:

| Field       | Type   | Description                                        |
| ----------- | ------ | -------------------------------------------------- |
| `id`        | string | Unique identifier for the download.                |
| `sessionId` | string | The session ID this download belongs to.           |
| `filename`  | string | The filename of the downloaded file.               |
| `mimeType`  | string | The MIME type of the file.                         |
| `size`      | number | File size in bytes.                                |
| `checksum`  | string | SHA256 checksum of the file.                       |
| `createdAt` | string | Timestamp when the file was downloaded (ISO 8601). |

<CardGroup cols={3}>
  <Card title="List downloads" icon="list" iconType="sharp-solid" href="/reference/api/list-downloads">
    List and filter downloads
  </Card>

  <Card title="Get download" icon="download" iconType="sharp-solid" href="/reference/api/get-download">
    Get metadata or file content
  </Card>

  <Card title="Delete download" icon="trash" iconType="sharp-solid" href="/reference/api/delete-download">
    Remove a download
  </Card>
</CardGroup>
