When to Use
- Multi-step browser automation: Log in to a site, navigate multiple pages, fill forms, and collect structured data across all steps — tasks too complex for the
scrape operation.
- Custom data extraction logic: Run JavaScript on the page (via
page.evaluate) to traverse the DOM, call internal APIs, or access window variables that are not exposed in the HTML source.
- Automated form submission: Fill and submit a web form programmatically and capture the response or confirmation data.
- A/B test interaction simulation: Trigger UI interactions (clicks, hovers, typed input) and measure resulting DOM or network changes.
- Advanced scraping with pagination: Implement custom pagination logic (click "Next", detect end-of-list) inside a single execute call to retrieve all pages of results.
Security note: The code field executes arbitrary JavaScript on the Browserless server. Use workflow secrets and the context object to pass dynamic values — never interpolate sensitive secrets directly into the code string, as that value is logged by the workflow engine.
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 executed in the Browserless function context. The function signature is async ({ page, context }) => { ... }. The function can return any JSON-serialisable value, a Buffer, or a string. The Puppeteer page object is fully available. |
context | Optional | JSON object passed to your function as the context argument. Use this to inject dynamic workflow values (URLs, IDs, credentials) without interpolating them into the code string. |
blockAds | Optional | Default true. Block ad networks during any page loads triggered by your code. |
timeout | Optional | Request timeout in milliseconds. Set generously for multi-step automation. |
useCustomBody | Optional | Send customBodyJson verbatim as the request body. |
customBodyJson | Optional | Raw JSON body when useCustomBody is true. |
Code shape: The Browserless function API expects the code body to export or return a function: async ({ page, context }) => { /* ... */ return result; }. The return value is serialised and returned as the response. Return a plain object for JSON, a string for text, or a Buffer for binary data.
Sample Configuration
{
"operation": "execute",
"baseUrl": "https://chrome.browserless.io",
"token": "{{ $env.BROWSERLESS_TOKEN }}",
"code": "async ({ page, context }) => { await page.goto(context.url, { waitUntil: 'networkidle2' }); const title = await page.title(); const price = await page.$eval(context.priceSelector, el => el.textContent.trim()); return { title, price }; }",
"context": {
"url": "{{ $input.productUrl }}",
"priceSelector": ".product-price"
},
"timeout": 30000,
"blockAds": true
}
Validation Errors
| Error Code | Cause |
VAL_MISSING_CODE | The code field is empty. |
CFG_INVALID | Node settings could not be resolved to an execute configuration. |
BROWSERLESS_BAD_REQUEST | The code contains a syntax error or the request body is malformed. |
Output
Success Port
| Field | Type | Description |
status | string | Always "success". |
errorCode | string | Always empty on success. |
operation | string | Always "execute". |
contentType | string | MIME type returned by the function, e.g. "application/json", "text/plain", "application/octet-stream". |
responseType | string | Discriminator: "json", "text", or "binary". |
jsonData | object | Populated when responseType is "json". Contains the parsed JSON return value of your function. |
textData | string | Populated when responseType is "text". |
binaryData | bytes | Populated when responseType is "binary". |
Error Port
| Field | Type | Description |
status | string | Always "error". |
errorCode | string | Machine-readable error code. |
errorMessage | string | Human-readable failure description. |
operation | string | Always "execute". |
httpStatusCode | int | HTTP status code from Browserless, if available. |
Sample Output
{
"status": "success",
"errorCode": "",
"operation": "execute",
"contentType": "application/json",
"responseType": "json",
"jsonData": {
"title": "Widget Pro 2026 Edition",
"price": "$49.99"
},
"textData": null,
"binaryData": null
}
Expression Reference
| Expression | Value |
{{ $output.BrowserlessNode.responseType }} | "json", "text", or "binary". |
{{ $output.BrowserlessNode.jsonData }} | Parsed JSON return value of your function. |
{{ $output.BrowserlessNode.jsonData.title }} | A specific field from the JSON result. |
{{ $output.BrowserlessNode.textData }} | Text result when responseType is "text". |
{{ $output.BrowserlessNode.contentType }} | MIME type of the response. |
Node Policies & GuardRails
| Policy | Recommendation | Severity |
| Code injection risk | Never interpolate untrusted user input into the code string. Pass all dynamic values via the context object. | Critical |
| Secret exposure in logs | Secrets passed in the code string appear in workflow logs. Always inject credentials through context using workflow secrets. | Critical |
| Token storage | Store the Browserless API token in a workflow secret. | Critical |
| Timeout for automation | Multi-step automation (login + navigate + extract) can take tens of seconds. Set timeout accordingly — the default 30 s is often too low. | Medium |
| Error handling in code | Wrap your function body in try/catch and return a structured error object so the workflow can distinguish JS-level errors from Browserless infrastructure errors. | Medium |
Examples
Example 1 — Log in and extract account data
{
"operation": "execute",
"baseUrl": "https://chrome.browserless.io",
"token": "{{ $env.BROWSERLESS_TOKEN }}",
"code": "async ({ page, context }) => { await page.goto(context.loginUrl, { waitUntil: 'networkidle2' }); await page.type('#email', context.email); await page.type('#password', context.password); await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle2' }), page.click('#login-btn')]); const balance = await page.$eval('.account-balance', el => el.textContent.trim()); return { balance }; }",
"context": {
"loginUrl": "https://portal.example.com/login",
"email": "{{ $env.PORTAL_EMAIL }}",
"password": "{{ $env.PORTAL_PASSWORD }}"
},
"timeout": 60000,
"blockAds": true
}
Example 2 — Call an internal page API and return JSON
{
"operation": "execute",
"baseUrl": "https://chrome.browserless.io",
"token": "{{ $env.BROWSERLESS_TOKEN }}",
"code": "async ({ page, context }) => { await page.goto(context.baseUrl, { waitUntil: 'domcontentloaded' }); const data = await page.evaluate(async (apiPath) => { const res = await fetch(apiPath); return res.json(); }, context.apiPath); return data; }",
"context": {
"baseUrl": "https://app.example.com",
"apiPath": "/api/internal/metrics"
},
"timeout": 20000
}
Example 3 — Paginated scrape returning all items
{
"operation": "execute",
"baseUrl": "https://chrome.browserless.io",
"token": "{{ $env.BROWSERLESS_TOKEN }}",
"code": "async ({ page, context }) => { const results = []; await page.goto(context.url, { waitUntil: 'networkidle2' }); while (true) { const items = await page.$$eval('.item-title', els => els.map(e => e.textContent.trim())); results.push(...items); const next = await page.$('.pagination-next:not(.disabled)'); if (!next) break; await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle2' }), next.click()]); } return { items: results, total: results.length }; }",
"context": { "url": "{{ $input.listingUrl }}" },
"timeout": 120000,
"blockAds": true
}