Skip to main content
Version: v1

/download API

Much like the /function API, the /download API allows you to POST code for browserless to execute. The only difference with /download is that, instead of returning the result of your function, we instead return a file that the browser downloads.

Check out this API schema defined in Swagger.

If your download request doesn't result in a file being downloaded, browserless will likely time out the function.

Basic Usage

Here's an example of downloading a file created in the browser (a CSV file):

const puppeteer = require('puppeteer');

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

// Here we generate a CSV file and have the browser download it
await page.evaluate(() => {
const rows = [
['name1', 'city1', 'some other info'],
['name2', 'city2', 'more info'],
let csvContent = 'data:text/csv;charset=utf-8,';
rows.forEach(function (rowArray) {
let row = rowArray.join(',');
csvContent += row + '\r\n';
const encodedUri = encodeURI(csvContent);
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', 'data.csv');



This might come as a surprise, but unfortunately in puppeteer there's no way to know if the file was downloaded, or an API to even get it. You'll have to know ahead of time where files are kept, and watch the file system for it to complete. Instead of wiring up all that code you can send browserless an HTTP request and it takes care of all the underlying file-system calls:

curl -X POST \ \
-H 'Content-Type: application/javascript' \
-d 'module.exports = async ({ page }) => {
await page.evaluate(() => {
const rows = [
["name1", "city1", "some other info"],
["name2", "city2", "more info"]
let csvContent = "data:text/csv;charset=utf-8,";
let row = rowArray.join(",");
csvContent += row + "\r\n";
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "data.csv");


This API is sensitive to the downloaded file and will return an appropriate Content-Type with the response.

Parameterizing your /download call

You can also post an application/json body, as opposed to an application/javascript one, if you'd like to re-use your function with new parameters. In this case, let's change our CSV file to be dynamic (note that our script is now minified for portability):

curl -X POST \ \
-H 'Content-Type: application/json' \
-d '{
"code": "module.exports=async({page:a,context:b})=>{const{rows:c}=b;await a.evaluate(()=>{let d='\''data:text/csv;charset=utf-8,'\'';c.forEach(function(g){let h=g.join('\'','\'');d+=h+'\''\r\n'\''});const e=encodeURI(d),f=document.createElement('\''a'\'');return f.setAttribute('\''href'\'',e),f.setAttribute('\''download'\'','\''data.csv'\''),document.body.appendChild(f),})};",
"context": {
"rows": [
["Some", "Cool", "CSV"],
["With", "Rows", "And Stuff"]

This lets you define re-usable functions and invoke them over HTTP in a more flexible way.