> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://platform.bctrl.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://platform.bctrl.ai/_mcp/server.

# Load a Browser Extension

> Import an extension from the Chrome Web Store, load it into a runtime, and verify it's working.

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.

```ts
import { Bctrl } from "@bctrl/sdk";
import { chromium } from "playwright";

const bctrl = new Bctrl({ apiKey: process.env.BCTRL_API_KEY! });

// 1. Import straight from the Chrome Web Store - one time, reusable forever.
const extension = await bctrl.browserExtensions.import({
  url: "https://chrome.google.com/webstore/detail/ublock-origin-lite/ddkjiahejlhfcafbddmgiahcphecmpfh",
  name: "uBlock Origin Lite",
});

// 2. Load it into a runtime at launch.
const runtime = await bctrl.runtimes.create({
  type: "browser",
  name: "extension-recipe",
  config: { extensionIds: [extension.id] },
});
const { connectUrl } = await bctrl.runtimes.start(runtime.id);

// 3. Verify with your own eyes: same page, ads gone.
const browser = await chromium.connectOverCDP(connectUrl);
const context = browser.contexts()[0] ?? (await browser.newContext());
const page = context.pages()[0] ?? (await context.newPage());

await page.goto("https://www.speedtest.net"); // an ad-heavy page
await page.waitForTimeout(5_000);
await page.screenshot({ path: "with-adblock.png", fullPage: true });

await browser.close();
await 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](/cookbook/persistent-sessions) 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](/cookbook/stealth-browsing). For pure traffic savings, prefer the built-in `networkTraffic` blocking ([Trim Network Traffic](/cookbook/trim-network-traffic)) - it works at the proxy layer and adds nothing detectable to the browser.

## Next

* [Extensions](/sdk/extensions) - upload, import, and manage
* [Trim Network Traffic](/cookbook/trim-network-traffic) - blocking without an extension
* [Runtime configuration](/sdk/runtime-config) - everything else in `config`