When to Use
- Report portal automation: Log in to a legacy reporting portal, click "Export CSV", and capture the download to forward to a data pipeline or store in S3.
- Government and regulatory data retrieval: Download statutory filings, compliance certificates, or data exports from portals that only expose data through browser download triggers.
- Supplier invoice collection: Navigate a supplier's portal, select a date range, and click "Download PDF" to collect invoices for accounts-payable workflows.
- App export automation: Trigger a data export from a SaaS tool (e.g. CRM contacts, analytics exports) that requires a human click to initiate.
- Secure document delivery: Retrieve signed documents or certificates from portals that do not expose direct download links — only a browser action triggers the file.
download vs. execute: Both operations run custom JavaScript. Use
download when the goal is to capture a file that the browser downloads (Content-Disposition: attachment). Use
execute when you want to return structured data or perform multi-step navigation without a file download.
Configuration
Connection
| Field | Required | Description |
BaseUrl | Required | Browserless instance URL. |
Token | Optional | API token for cloud Browserless. |
Operation
| Field | Required | Description |
code | Required | JavaScript string that triggers the file download inside the browser. The function signature is async ({ page, context }) => { ... }. Navigate to the target URL, perform the actions that trigger the download, and the Browserless service intercepts the download response and returns the file bytes. |
context | Optional | JSON object passed to the function as the context argument. Use this to inject URLs, credentials, date ranges, or other dynamic values without interpolating them into the code string. |
blockAds | Optional | Default true. Block ad networks during navigation. |
timeout | Optional | Request timeout in milliseconds. Set generously — login + navigate + trigger can take 30–60 s. |
useCustomBody | Optional | Send customBodyJson verbatim as the request body. |
customBodyJson | Optional | Raw JSON body when useCustomBody is true. |
Security note: Never interpolate credentials or secrets directly into the code string — they appear in workflow logs. Always pass dynamic values through the context object using workflow secrets.
Sample Configuration
{
"operation": "download",
"baseUrl": "https://chrome.browserless.io",
"token": "{{ $env.BROWSERLESS_TOKEN }}",
"code": "async ({ page, context }) => { await page.goto(context.loginUrl, { waitUntil: 'networkidle2' }); await page.type('#username', context.username); await page.type('#password', context.password); await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle2' }), page.click('#btn-login')]); await page.goto(context.reportUrl, { waitUntil: 'networkidle2' }); await page.click('#export-csv-btn'); }",
"context": {
"loginUrl": "https://reports.example.com/login",
"reportUrl": "https://reports.example.com/monthly/{{ $input.month }}",
"username": "{{ $env.REPORTS_USER }}",
"password": "{{ $env.REPORTS_PASS }}"
},
"timeout": 90000,
"blockAds": true
}
Validation Errors
| Error Code | Cause |
VAL_MISSING_CODE | The code field is empty. |
CFG_INVALID | Node settings could not be resolved to a download configuration. |
BROWSERLESS_BAD_REQUEST | customBodyJson is not valid JSON or does not match the download request shape. |
Output
Success Port
| Field | Type | Description |
status | string | Always "success". |
errorCode | string | Always empty on success. |
operation | string | Always "download". |
mimeType | string | MIME type of the downloaded file, e.g. "text/csv", "application/pdf", "application/octet-stream". |
fileName | string | File name from the Content-Disposition header, e.g. "report-2026-05.csv". |
sizeBytes | int | File size in bytes. |
data | bytes | Raw file bytes. Pass to an S3 upload, email attachment, or file-write node. |
Error Port
| Field | Type | Description |
status | string | Always "error". |
errorCode | string | Machine-readable error code. |
errorMessage | string | Human-readable failure description. |
operation | string | Always "download". |
httpStatusCode | int | HTTP status code from Browserless, if available. |
Sample Output
{
"status": "success",
"errorCode": "",
"operation": "download",
"mimeType": "text/csv",
"fileName": "monthly-report-2026-05.csv",
"sizeBytes": 18432,
"data": "<binary CSV bytes>"
}
Expression Reference
| Expression | Value |
{{ $output.BrowserlessNode.mimeType }} | MIME type of the downloaded file. |
{{ $output.BrowserlessNode.fileName }} | File name from the Content-Disposition header. |
{{ $output.BrowserlessNode.sizeBytes }} | File size in bytes. |
{{ $output.BrowserlessNode.data }} | Raw file bytes — pass directly to upload or write nodes. |
Node Policies & GuardRails
| Policy | Recommendation | Severity |
| Code injection risk | Never interpolate untrusted user input into the code string. Use the context object for all dynamic values. | Critical |
| Credential security | Always pass usernames and passwords via workflow secrets referenced in context — never in the code string. | Critical |
| Token storage | Store the Browserless API token in a workflow secret. | Critical |
| File size limits | Large file downloads (100 MB+) may exceed workflow memory limits. Validate expected file sizes and consider streaming to storage directly. | Medium |
| Timeout for multi-step flows | Login + navigate + trigger + wait for download can take 30–90 s. Set timeout to at least 2× the expected duration. | Medium |
Examples
Example 1 — Download a CSV export from a reporting portal
{
"operation": "download",
"baseUrl": "https://chrome.browserless.io",
"token": "{{ $env.BROWSERLESS_TOKEN }}",
"code": "async ({ page, context }) => { await page.goto(context.url, { waitUntil: 'networkidle2' }); await page.waitForSelector('#export-btn'); await page.click('#export-btn'); }",
"context": {
"url": "https://analytics.example.com/export?from={{ $input.from }}&to={{ $input.to }}&token={{ $env.ANALYTICS_TOKEN }}"
},
"timeout": 60000
}
Example 2 — Download an invoice PDF after login
{
"operation": "download",
"baseUrl": "https://chrome.browserless.io",
"token": "{{ $env.BROWSERLESS_TOKEN }}",
"code": "async ({ page, context }) => { await page.goto(context.loginUrl, { waitUntil: 'networkidle2' }); await page.type('[name=email]', context.email); await page.type('[name=password]', context.password); await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle2' }), page.click('[type=submit]')]); await page.goto(context.invoiceUrl, { waitUntil: 'networkidle2' }); await page.waitForSelector('.download-invoice'); await page.click('.download-invoice'); }",
"context": {
"loginUrl": "https://billing.example.com/login",
"invoiceUrl": "https://billing.example.com/invoices/{{ $input.invoiceId }}",
"email": "{{ $env.BILLING_EMAIL }}",
"password": "{{ $env.BILLING_PASSWORD }}"
},
"timeout": 90000,
"blockAds": true
}
Example 3 — Download a government data export
{
"operation": "download",
"baseUrl": "https://chrome.browserless.io",
"token": "{{ $env.BROWSERLESS_TOKEN }}",
"code": "async ({ page, context }) => { await page.goto(context.searchUrl, { waitUntil: 'networkidle2' }); await page.select('#year-select', context.year); await page.click('#search-btn'); await page.waitForSelector('#download-results', { timeout: 15000 }); await page.click('#download-results'); }",
"context": {
"searchUrl": "https://data.gov.example.com/filings/search",
"year": "{{ $input.year }}"
},
"timeout": 120000
}