Load a Browser Extension

View as Markdown

Some jobs are easier with an extension in the browser - an ad blocker for cleaner pages, a cookie-banner dismisser so your automation never sees the popup, or your own internal tooling packed as a .crx. Import it once, then load it into any runtime by id.

1import { Bctrl } from "@bctrl/sdk";
2import { chromium } from "playwright";
3
4const bctrl = new Bctrl({ apiKey: process.env.BCTRL_API_KEY! });
5
6// 1. Import straight from the Chrome Web Store - one time, reusable forever.
7const extension = await bctrl.browserExtensions.import({
8 url: "https://chrome.google.com/webstore/detail/ublock-origin-lite/ddkjiahejlhfcafbddmgiahcphecmpfh",
9 name: "uBlock Origin Lite",
10});
11
12// 2. Load it into a runtime at launch.
13const runtime = await bctrl.runtimes.create({
14 type: "browser",
15 name: "extension-recipe",
16 config: { extensionIds: [extension.id] },
17});
18const { connectUrl } = await bctrl.runtimes.start(runtime.id);
19
20// 3. Verify with your own eyes: same page, ads gone.
21const browser = await chromium.connectOverCDP(connectUrl);
22const context = browser.contexts()[0] ?? (await browser.newContext());
23const page = context.pages()[0] ?? (await context.newPage());
24
25await page.goto("https://www.speedtest.net"); // an ad-heavy page
26await page.waitForTimeout(5_000);
27await page.screenshot({ path: "with-adblock.png", fullPage: true });
28
29await browser.close();
30await bctrl.runtimes.stop(runtime.id);

📸 Content TODO: side-by-side screenshots of the same page with and without the extension loaded - run the recipe once with extensionIds and once without.

Your own extensions work the same way - upload a packed .crx as a Blob with browserExtensions.upload({ file, name }) instead of importing. That’s the only packaging format supported today.

Extensions load at launch, so extensionIds is part of runtime config - set it at create time, or update it while the runtime is stopped. On profile-backed runtimes extensions are managed by the platform for the life of the profile.

Mind the fingerprint

Extensions are detectable by the pages you visit, and a browser whose extension set contradicts its story is a signal - see Configure a Stealth Browser. For pure traffic savings, prefer the built-in networkTraffic blocking (Trim Network Traffic) - it works at the proxy layer and adds nothing detectable to the browser.

Next