
Building Chrome Extensions Without a Backend: The Privacy-First Architecture Pattern
The real cost of a backend proxy
Most browser extensions that call external APIs follow the same pattern: user data flows into the browser, gets shipped to a developer-controlled server, and then heads to the actual API. It's understandable. You want error logging, caching, maybe some analytics. Standing up a proxy is familiar work.
But that architecture has a hidden cost that accumulates quietly: you now own everything that touches your server. Breach risk. Retention obligations. GDPR/CCPA compliance surface area. The possibility that somewhere in your logging stack, someone's highlighted text from a medical article ends up in a database you forgot about.
The rabbitholes Chrome extension takes a different approach, and it's worth studying the architecture even if you never ship a similar product.
<> "Most browser extensions that call external APIs route your data through a developer-controlled server first. That's not a conspiracy — it's the path of least resistance."/>
The design is straightforward in concept, harder to execute carefully: the extension sends highlighted text directly from the browser to the API provider. No intermediary. Requests go from the Chrome service worker straight to api.anthropic.com (Claude Haiku) and optionally api.search.brave.com. The developer's infrastructure never sees your prompts.
---
How the architecture actually works
The key to making this work is where API secrets live and how requests are mediated. Instead of storing a shared server-side API key (which would require your backend), the user supplies their own Anthropic API key. It's stored in chrome.storage.sync—encrypted, scoped to the browser profile, never transmitted to a third-party endpoint.
The flow looks like this:
1User highlights text
2 ↓
3Content script detects selection
4 ↓
5Message sent to background service worker
6 ↓
7Service worker reads API key from chrome.storage.sync
8 ↓
9Direct HTTPS request → api.anthropic.com
10 ↓
11Response returned to content script
12 ↓
13Shadow DOM tooltip rendered (without modifying host page DOM)Notice the service worker step. This is important. You don't want your API key leaking into page context, where malicious page scripts could potentially read it. Mediating all API calls through the background service worker keeps the secret out of the content script's scope.
A simplified version of this pattern looks like:
1// background.ts (service worker)
2chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
3 if (message.type === 'EXPLAIN_TEXT') {
4 handleExplainRequest(message.text).then(sendResponse);
5 return true; // keep message channel open for async response
6 }
7});
8The content script just sends a message and waits. It never touches the API key.
---
The shadow DOM detail worth noting
Rabbitholes renders its tooltip using a shadow DOM, which means it doesn't inject markup into the host page's normal DOM tree. This is thoughtful design for a different reason than privacy—it prevents style collisions and avoids breaking page functionality. If you're building any kind of overlay UI in a content script, shadow DOM should be your default.
1// content.ts
2const host = document.createElement('div');
3const shadow = host.attachShadow({ mode: 'closed' });
4
5const tooltip = document.createElement('div');
6tooltip.textContent = explanationText;
7shadow.appendChild(tooltip);
8document.body.appendChild(host);Small detail, but it signals that the author thought carefully about not just privacy but also correctness as a hosted guest on arbitrary pages.
---
The trade-offs you need to own
This architecture isn't free. If you adopt it, you're signing up for some real constraints:
Users must bring their own API keys. This is a significant friction point for consumer-facing tools. Developers or technical users handle it fine; mainstream users often won't. If your audience is non-technical, this may be a dealbreaker.
You lose abuse prevention leverage. With no backend, you can't rate-limit by user, detect anomalous usage patterns, or cut off a bad actor at the server level. You're dependent on the API provider's own controls.
Analytics and error monitoring become harder. You can still use something like Sentry for JavaScript errors, but you need to be extremely careful that your error reporting doesn't accidentally capture prompt content in stack traces or breadcrumbs.
Cross-device features are limited. chrome.storage.sync does sync across devices for the same Chrome profile, but anything requiring server-side state (shared workspaces, history across browsers, collaboration) needs a backend.
---
When to apply this pattern
This architecture is genuinely the right call when:
- Privacy is a core product promise, not a nice-to-have
- Your users are technical enough to manage their own API keys
- You don't need server-side features
- You want to reduce operational overhead dramatically (no servers to monitor, no data to protect)
- You want to be honest in your privacy policy—"we don't see your data" is a much stronger statement when it's architecturally true
For developer tools, research tools, anything touching sensitive documents—this pattern is worth the trade-offs.
---
Why this matters beyond one extension
The broader point isn't really about rabbitholes specifically. It's about a habit the industry has developed: defaulting to backend proxies even when they're not needed, because that's the familiar architecture.
Every unnecessary backend is:
- A new attack surface
- A compliance obligation
- Infrastructure to maintain
- A potential source of user data exposure
For small teams especially, eliminating a backend you don't actually need is a significant win. And for users who are increasingly aware that "free" AI tools often monetize their inputs, a verifiable "your data never hits our servers" claim is real product differentiation.
If you're building a browser extension that calls an AI API, the first question to ask isn't "how do I set up my proxy server." It's: does this actually need a server at all?
Often, the honest answer is no.

