Skip to main content

/download API

The /download API runs your Puppeteer code in a browser and returns files Chrome downloaded, with appropriate Content-Type and Content-Disposition headers. You can load external libraries via import and use ESM modules that run inside the browser context. If your download request doesn't result in a file being downloaded, Browserless will time out the function.

You can check the full Open API schema here.

When to Use Which Format

The /download API supports two request formats:

  • Raw JavaScript (Content-Type: application/javascript) - Simple and supports ESM imports directly. Best for straightforward use cases.
  • JSON Payload (Content-Type: application/json) - Easier for sending long or minified code and passing context variables. Best when you need to parameterize your code.

Method 1: Send JavaScript Code (application/javascript)

This method sends your JavaScript code directly as the request body with Content-Type: application/javascript.

Basic Example

curl -X POST \
https://production-sfo.browserless.io/download?token=YOUR_API_TOKEN_HERE \
-H 'Content-Type: application/javascript' \
-d 'export default async function ({ page }) {
await page.evaluate(() => {
const json = { ping: "pong", rnd: [...Array(5)].map(() => Math.random()) };
const jsonContent = `data:application/json,${JSON.stringify(json)}`;
const encodedUri = encodeURI(jsonContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "data.json");
document.body.appendChild(link);
link.click();
});
await new Promise(r => setTimeout(r, 1000));
};'

Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Disposition: attachment; filename="data.json"

{"ping":"pong","rnd":[0.854246,0.114512,0.212580,0.482122,0.107878]}

Using External Libraries

The /download API supports ECMAScript modules, allowing you to use import syntax to load modules directly from HTTP URLs. For example, let's import the Faker.js library:

JS Code

import { faker } from "https://esm.sh/@faker-js/faker";

export default async function ({ page }) {
const rndName = faker.person.fullName();
const rndEmail = faker.internet.email();

await page.evaluate(
(name, email) => {
const jsonStr = JSON.stringify({ name, email });
const jsonContent = `data:application/json,${jsonStr}`;
const encodedUri = encodeURI(jsonContent);
const link = document.createElement("a");

link.setAttribute("href", encodedUri);
link.setAttribute("download", "data.json");
document.body.appendChild(link);
return link.click();
},
rndName,
rndEmail
);
}
curl -X POST \
"https://production-sfo.browserless.io/download?token=YOUR_API_TOKEN_HERE" \
-H 'Content-Type: application/javascript' \
-d 'import { faker } from '\''https://esm.sh/@faker-js/faker'\'';

export default async function ({ page }) {
const rndName = faker.person.fullName();
const rndEmail = faker.internet.email();

await page.evaluate((name, email) => {
const jsonStr = JSON.stringify({name, email});
const jsonContent = `data:application/json,${jsonStr}`;
const encodedUri = encodeURI(jsonContent);
const link = document.createElement("a");

link.setAttribute("href", encodedUri);
link.setAttribute("download", "data.json");
document.body.appendChild(link);
return link.click();
}, rndName, rndEmail);
}'

Response

{
"name": "Jasmine Littel",
"email": "Giovanna26@hotmail.com"
}

Method 2: Send JSON Payload (code + context)

This method sends a JSON payload with Content-Type: application/json. This is useful for sending long or minified code and passing context variables to your function.

The JSON payload must include:

  • code: String, required — your JavaScript code as a string
  • context: Object, optional — variables to pass to your function

Example

JS Code

export default async function ({ page, context }) {
await page.evaluate((context) => {
const json = {
url: context.url,
ping: "pong",
rnd: [...Array(context.arrayLen)].map(() => Math.random()),
};
const jsonContent = `data:application/json,${JSON.stringify(json)}`;
const encodedUri = encodeURI(jsonContent);
const link = document.createElement("a");

link.setAttribute("href", encodedUri);
link.setAttribute("download", "data.json");
document.body.appendChild(link);
return link.click();
}, context);
}
curl --request POST \
--url 'https://production-sfo.browserless.io/download?token=YOUR_API_TOKEN_HERE' \
--header 'Content-Type: application/json' \
--data '{
"code": "export default async function({page:t,context:a}){await t.evaluate(t=>{let a={url:t.url,ping:`pong`,rnd:[...Array(t.arrayLen)].map(()=>Math.random())},e=`data:application/json,${JSON.stringify(a)}`,n=encodeURI(e),r=document.createElement(`a`);return r.setAttribute(`href`,n),r.setAttribute(`download`,`data.json`),document.body.appendChild(r),r.click()},a)};",
"context": {
"url": "https://browserless.io/",
"arrayLen": 10
}
}'

Response

{
"url": "https://browserless.io/",
"ping": "pong",
"rnd": [0.123456, 0.234567, 0.345678, 0.456789, 0.567890, 0.678901, 0.789012, 0.890123, 0.901234, 0.012345]
}

Reference: Local Puppeteer Comparison

Click to see how this works with local Puppeteer

Here's an example of downloading a file using local Puppeteer:

import puppeteer from "puppeteer";

async function run() {
const browser = await puppeteer.launch();
const page = await browser.newPage();

// Here we generate a json file and have the browser download it
await page.evaluate(() => {
const json = {
ping: "pong",
rnd: [...Array(5)].map(() => Math.random()),
};
const jsonContent = `data:application/json,${JSON.stringify(json)}`;
const encodedUri = encodeURI(jsonContent);
const link = document.createElement("a");

link.setAttribute("href", encodedUri);
link.setAttribute("download", "data.json");
document.body.appendChild(link);
return link.click();
});
await browser.close();
}

run();

Limitations of Local Puppeteer Setup:

  • Downloads go to your local Downloads folder
  • No easy way to programmatically access downloaded files
  • Requires complex file system monitoring
  • High resource usage (runs full Chrome locally)