← Blog

Claude API Browser Extension Integration Guide

2026-10-03 · 5 min read · SubToAPI Team

If you're trying to wire Claude into a Chrome or Firefox extension, you've probably already hit the wall: calling api.anthropic.com directly from a content script or extension popup either fails on CORS, or forces you to ship your API key inside the extension bundle — which anyone can extract with a few minutes and devtools.

This article covers the actual integration pattern that works for browser extensions: a thin backend between your extension and Claude, how to structure the Manifest V3 permissions and messaging, and how to stream Claude's response into a popup or sidebar without blocking the UI thread.

Why you can't call Claude directly from an extension

Two separate problems show up the moment you try:

The fix isn't a CORS workaround — it's moving the Claude call server-side and having the extension talk to that server instead.

The architecture that actually works

Extension (popup/content script)
      │  fetch() with your app's own key
      ▼
Backend proxy (your server or SubToAPI)
      │  Authorization: Bearer <real Claude credential>
      ▼
Claude

The extension never touches an Anthropic key. It talks to an HTTPS endpoint — either your own backend or a managed layer like SubToAPI — using a key that's scoped to your application, easy to revoke, and safe to embed because it has no admin privileges and can be rate-limited per install.

SubToAPI is built for exactly this pattern: it turns your Claude access into a standard HTTPS API with sub_live_... keys, so your extension calls a normal REST endpoint instead of managing Anthropic auth, CORS, and streaming plumbing yourself.

Manifest V3 setup

Declare the host permission for your backend (not for anthropic.com — your extension never calls it):

{
  "manifest_version": 3,
  "name": "Claude Sidebar Assistant",
  "version": "1.0",
  "background": { "service_worker": "background.js" },
  "permissions": ["storage"],
  "host_permissions": ["https://api.subtoapi.app/*"],
  "action": { "default_popup": "popup.html" }
}

Keep the actual request in the background service worker, not the content script. Content scripts run in the context of the page and are more exposed; the background worker is a cleaner boundary for making network calls and holding short-lived tokens in memory.

Calling the API from the background worker

// background.js
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.type !== "ASK_CLAUDE") return;

  fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${msg.appKey}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-3-5-sonnet-20241022",
      max_tokens: 1024,
      messages: [{ role: "user", content: msg.prompt }]
    })
  })
    .then(res => res.json())
    .then(data => sendResponse({ ok: true, data }))
    .catch(err => sendResponse({ ok: false, error: err.message }));

  return true; // keep the message channel open for async response
});

The popup sends a message with the user's prompt and receives the result — it never sees or stores the Anthropic credential, only a scoped application key. Full request/response shape is in the messages docs.

Streaming into the popup UI

A non-streaming popup feels laggy for anything longer than a sentence. Extensions support fetch with streaming bodies in the background worker, and you can relay chunks to the popup via chrome.runtime.Port:

// background.js
chrome.runtime.onConnect.addListener(port => {
  port.onMessage.addListener(async (msg) => {
    const res = await fetch("https://api.subtoapi.app/v1/messages", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${msg.appKey}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        model: "claude-3-5-sonnet-20241022",
        max_tokens: 1024,
        stream: true,
        messages: [{ role: "user", content: msg.prompt }]
      })
    });

    const reader = res.body.getReader();
    const decoder = new TextDecoder();
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      port.postMessage({ chunk: decoder.decode(value) });
    }
    port.postMessage({ done: true });
  });
});

On the popup side, connect with chrome.runtime.connect() and append each chunk to the DOM as it arrives. Details on the event format and reconnect behavior are in the streaming guide.

Storing the application key safely

Don't hardcode even a scoped app key in source. Instead:

If you're the one issuing these keys to your users, a dashboard that supports per-key revocation and usage tracking saves you from writing that infrastructure yourself. That's the core of what SubToAPI's key management covers alongside the API itself.

Handling errors and rate limits gracefully

Browser extensions run on unpredictable networks (corporate proxies, VPNs, flaky Wi-Fi) and users will click the action button repeatedly if nothing visibly happens. At minimum:

Getting started

To wire this up end to end: sign up at /signup, generate an app key, and follow the quickstart to send your first request from a local script before moving the call into your extension's background worker. Pricing starts at €9/month for the Solo plan, with Team and Scale tiers for shared or production workloads — see /pricing.

How do I avoid CORS errors calling Claude from a browser extension?

You don't fight CORS — you route the request through a backend (your own server or a service like SubToAPI) that calls Claude server-side and returns JSON or a stream to your extension over a normal HTTPS endpoint.

Is it safe to store an Anthropic API key inside a browser extension?

No. Extension code is readable by anyone who installs it. Use a scoped, revocable application key issued by a backend proxy instead, and keep the real provider credential server-side only.

Can a Manifest V3 extension stream Claude responses in real time?

Yes — make the streaming fetch request in the background service worker and relay chunks to the popup or content script using chrome.runtime.Port messaging as they arrive.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →