For AI agents: a documentation index is available at /llms.txt
Skip to main content

Browser Extensions

Browserless allows you to upload and use your own browser extensions in your automation sessions. Extensions must be uploaded through your account dashboard and can then be referenced by name in your launch options.

Prerequisites
  • A Browserless API token from your account dashboard
  • Puppeteer or Playwright installed locally
note

Extension support is only available when using the Chromium browser. If you don't specify a browser, Chromium is used by default.

Extension Upload

  1. Navigate to the Extensions section in your account dashboard
  2. Upload your extension as a ZIP file containing the extension directory (max 100 MB)
  3. Once uploaded, extensions can be referenced by their assigned name
Browserless dashboard Extensions page showing an uploaded browser extension with its name, description, status, and a delete action
Demo Extension

You can find a demo extension in this link. This extension adds the "Hello from extension" text to the body element of every page, making it easy to verify that your extension is working correctly.

Extension Launch Parameter

Use the extensions parameter in your launch options to load extensions:

import puppeteer from "puppeteer-core";

// Define launch options
const launchArgs = {
extensions: ['extension_name_01', 'extension_name_02']
};

// Create query parameters
const queryParams = new URLSearchParams({
token: 'YOUR_API_TOKEN_HERE',
timeout: '180000',
launch: JSON.stringify(launchArgs)
});

const browser = await puppeteer.connect({
browserWSEndpoint: `wss://production-sfo.browserless.io?${queryParams.toString()}`,
});
const page = await browser.newPage();
Important Implementation Detail

Extensions are loaded in the default context only. When using Playwright, you must create your pages from the default context (browser.contexts()[0]) to access the loaded extensions. Creating new contexts will not have access to the extensions.

Extension Requirements

  • ZIP structure: Upload extensions as ZIP files. The manifest.json must sit at the ZIP root directory, not nested inside a subdirectory. A ZIP containing my-extension/manifest.json fails; it must be manifest.json at the top level.
  • Required manifest fields: manifest.json must include name, version, and manifest_version. Missing any of these returns an upload error.
  • Extension naming: Names must match the pattern [a-zA-Z0-9_-] and be 1 to 99 characters long.
  • File size: Maximum 100 MB per extension.
  • Account limits: Maximum 10 extensions per account. Duplicate extension names are rejected.
  • Upload permissions: Only the account owner can upload extensions. Other team members cannot upload regardless of role.
  • Malware scanning: Uploaded extensions go through automated malware scanning before becoming available.
  • Supported routes: Extensions only work on /chromium and / (the default path). Paths containing chrome, edge, or playwright block extensions. Using an unsupported path returns: "Extensions are only supported for CDP-based libraries with Chromium".

Extensions with Authenticated Proxies

Extensions require CDP mode and the default browser context. Authenticated proxies in Playwright typically use browser.newContext({ proxy }), which creates a new context where extensions are not available. To use both together, pass the proxy server as a Chrome launch argument and handle authentication through the CDP Fetch domain.

import playwright from "playwright-core";

const launchArgs = {
extensions: ["my_extension"],
};

const queryParams = new URLSearchParams({
token: "YOUR_API_TOKEN_HERE",
"--proxy-server": "proxy-host:port",
launch: JSON.stringify(launchArgs),
});

const browser = await playwright.chromium.connectOverCDP(
`wss://production-sfo.browserless.io?${queryParams.toString()}`
);

const context = browser.contexts()[0];
const page = context.pages()[0] || (await context.newPage());

// Enable proxy authentication via CDP Fetch domain
const client = await context.newCDPSession(page);
await client.send("Fetch.enable", {
handleAuthRequests: true,
patterns: [{ urlPattern: "*" }],
});

client.on("Fetch.authRequired", async (event) => {
await client.send("Fetch.continueWithAuth", {
requestId: event.requestId,
authChallengeResponse: {
response: "ProvideCredentials",
username: "proxy_user",
password: "proxy_pass",
},
});
});

client.on("Fetch.requestPaused", async (event) => {
await client.send("Fetch.continueRequest", {
requestId: event.requestId,
});
});

await page.goto("https://example.com");
await browser.close();

This pattern keeps everything in the default context where extensions are loaded, while routing traffic through the authenticated proxy. For a deeper comparison of connection methods, see connect vs connectOverCDP.

Best Practices

  • Test Locally First: Always test your extension in a local Chrome browser before uploading
  • Keep Extensions Small: Larger extensions increase session startup time
  • Use Descriptive Names: Give your extensions clear, descriptive names for easier management
  • Version Control: Consider versioning your extensions if you make frequent updates

FAQ & Troubleshooting

My extension is not loading

Verify the extension name matches exactly what is shown in your dashboard. Check that the extension ZIP file contains the proper directory structure and that the extension is compatible with Chromium.

Sessions are taking too long to start

Reduce the number of extensions loaded simultaneously and check extension file sizes. Sessions with extensions take more time to start because the extension is loaded in real time. The startup time increases with the number and size of extensions.

My extension is not working as expected

Test the extension in a local Chrome browser first. Check the browser console for any extension errors and verify the extension's manifest.json is valid.

Why am I getting a 403 Forbidden error?

Your API token is missing or expired. Pass it as a ?token= query parameter in the WebSocket or HTTP URL. Verify the token in your account dashboard.

My script works locally but fails on Browserless

Local browser settings may differ from the Browserless environment. Use launch parameters to match your local setup (viewport, user agent, timezone). See launch parameters for the full list.

Next steps