Skip to main content

Browser Extensions

note

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

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.

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
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

Troubleshooting

Extension Not Loading?

  • Verify the extension name matches exactly what's shown in your dashboard
  • Check that the extension ZIP file contains the proper directory structure
  • Ensure the extension is compatible with Chromium

Session Taking Too Long to Start?

  • Reduce the number of extensions loaded simultaneously
  • Check extension file sizes
  • Consider if all extensions are necessary for your use case

Extension Not Working as Expected?

  • Test the extension locally first
  • Check browser console for any extension errors
  • Verify the extension's manifest.json is valid
note

Sessions with extensions can take more time to start since we need to load the extension in real time. The startup time increases with the number and size of extensions loaded.