Download API
Run custom Puppeteer code and return files that Chrome downloads during execution. The response includes appropriate Content-Type and Content-Disposition headers. You can load external libraries via import and use ESM modules that run inside the browser context.
Endpoint
- Method:
POST - Path:
/download - Auth:
tokenquery parameter (?token=) - Content-Type:
application/javascriptorapplication/json - Response:
*/*(based on downloaded file)
See the OpenAPI reference for complete details.
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 stringcontext: 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)