← Blog

Claude API Chrome Extension Tutorial: Step by Step

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

If you're searching for a Claude API Chrome extension tutorial, you're probably trying to build something like a page summarizer, a writing assistant, or a research sidebar that calls Claude directly from the browser. The good news is this is straightforward with Manifest V3. The tricky part — and the part most tutorials skip — is how you call the API securely without exposing your key in client-side code that anyone can inspect.

This guide walks through the full setup: project structure, manifest permissions, a background service worker that talks to Claude, and a popup UI that displays the response. Along the way we'll cover the one architectural decision that determines whether your extension is safe to ship or a liability waiting to leak credentials.

Why you can't call Claude directly from a Chrome extension

Manifest V3 extensions run JavaScript in a sandboxed environment, but that doesn't make them a safe place to store API secrets. Anything in your background.js or popup.js can be extracted by unpacking the extension's .crx file. If you hardcode an Anthropic API key in the extension bundle, it will eventually end up in a public GitHub repo or a reverse-engineered copy posted somewhere you don't control.

There's also a technical hurdle: Anthropic's API is designed for server-side use, so request signing and CORS behavior assume a backend caller, not an arbitrary browser extension making direct cross-origin calls.

The practical fix is to put a thin API layer between the extension and the model provider. That layer holds the real credentials, enforces rate limits, and gives you per-key usage data if multiple people install the extension. This is exactly what SubToAPI is for — it turns your Claude access into an HTTPS API with scoped sub_live_... keys you can safely embed in client apps, including browser extensions, because each key is independently revocable and rate-limited.

Step 1: Project structure

A minimal extension needs four files:

claude-extension/
├── manifest.json
├── background.js
├── popup.html
└── popup.js

Step 2: manifest.json

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

The host_permissions entry is what lets your service worker fetch from the API domain without extra prompts. Keep this list as narrow as possible — only the domains you actually call.

Step 3: background service worker

The service worker handles the actual API request. Store the SubToAPI key in chrome.storage.local (set it once from an options page, or hardcode it during local development only) rather than in plain JS.

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

  chrome.storage.local.get(["subtoapiKey"], async ({ subtoapiKey }) => {
    const res = await fetch("https://api.subtoapi.app/v1/messages", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${subtoapiKey}`
      },
      body: JSON.stringify({
        model: "claude-sonnet-4-5",
        max_tokens: 500,
        messages: [{ role: "user", content: msg.prompt }]
      })
    });

    const data = await res.json();
    sendResponse({ text: data.content[0].text });
  });

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

This keeps the network call out of the popup and content script context, which is generally safer and easier to debug with the background page's console.

Step 4: popup UI

<!-- popup.html -->
<textarea id="prompt" rows="4" placeholder="Summarize this page..."></textarea>
<button id="run">Ask Claude</button>
<pre id="output"></pre>
<script src="popup.js"></script>
// popup.js
document.getElementById("run").addEventListener("click", async () => {
  const prompt = document.getElementById("prompt").value;
  const output = document.getElementById("output");
  output.textContent = "Thinking...";

  chrome.runtime.sendMessage({ type: "ASK_CLAUDE", prompt }, (response) => {
    output.textContent = response.text;
  });
});

Load the folder via chrome://extensions → Developer mode → Load unpacked, and you have a working extension in under ten minutes.

Step 5: pulling in page content

Most useful extensions act on the active tab's content, not a blank text box. Add a content script to extract text and pass it along:

// Injected via chrome.scripting.executeScript
function getPageText() {
  return document.body.innerText.slice(0, 8000);
}

Send that text as part of the prompt sent to ASK_CLAUDE, and you've got a real page summarizer or explainer.

Step 6: streaming for a better UX

A popup that sits on "Thinking..." for ten seconds feels broken. Streaming responses token-by-token makes the extension feel responsive even on longer completions. If you're using SubToAPI, streaming is supported over the same endpoint with server-sent events — see /docs/streaming for the exact event format and how to parse it incrementally in the service worker before forwarding chunks to the popup.

Step 7: handling keys and distribution

Before publishing to the Chrome Web Store, decide how end users supply their own key. Two common patterns:

Either way, start with the free trial at /signup to get a key, test the full flow locally, and check /docs/quickstart for request/response shapes before wiring up the extension's error handling (rate limits, malformed prompts, timeouts).

Common pitfalls

Can I call the Claude API directly from a content script?

Technically you can attempt a fetch from a content script, but it inherits the page's CORS restrictions and is visible to any script running on that page. Route calls through the background service worker instead, which has a cleaner permission model.

Do I need a backend server for a Claude-powered extension?

Not necessarily. A service like SubToAPI gives you a secure HTTPS endpoint with scoped keys, so the background worker can call it directly without you running your own proxy server, as long as users supply their own key or you accept the shared-key tradeoffs above.

How do I avoid hitting Claude's rate limits with many extension users?

If every install shares one API key, concurrent usage adds up fast. Check your plan's limits on /pricing and consider issuing separate keys per user or team via the SubToAPI dashboard so usage and limits are tracked individually.

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 →