Beyond GPT Wrappers: What Real AI Extensions Do
Most "AI Chrome extensions" in 2026 are thin wrappers: they grab the selected text, send it to the OpenAI API with a generic prompt, and display the response in a popup. That is a 4-hour build, not a product. The extensions that reach tens of thousands of users do something fundamentally different — they integrate AI into the browser context in ways that feel native, not bolted on.
The best AI extensions understand the page the user is on. They extract structured information from the DOM, maintain context across tabs, interact with web APIs on behalf of the user, and produce outputs that are embedded back into the page in a way that feels seamless. That is what this guide covers: building AI extensions that actually belong in the browser.
Manifest V3 Architecture
Manifest V3 changed the mental model for extension architecture. The old approach — a persistent background page with a WebSocket connection and global state — no longer works. MV3 background scripts run as service workers that can be terminated at any time and restart on demand. This forces you to think carefully about state management and async communication patterns.
For AI extensions, the architecture typically has four pieces: the service worker (background.js) handles AI API calls and state coordination; content scripts run in the page context and extract DOM data; the side panel or popup provides the user interface; and chrome.storage.local persists data between service worker restarts.
// manifest.json — Manifest V3 structure for AI extension
{
"manifest_version": 3,
"name": "AI Page Assistant",
"version": "1.0.0",
"description": "AI-powered assistant that understands any web page",
"permissions": [
"activeTab",
"storage",
"sidePanel",
"contextMenus"
],
"host_permissions": [
"https://api.openai.com/*"
],
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": [""],
"js": ["content.js"],
"run_at": "document_idle"
}
],
"side_panel": {
"default_path": "sidepanel.html"
},
"action": {
"default_popup": "popup.html",
"default_title": "AI Page Assistant"
}
}
Extracting Rich Page Context
The quality of your AI output is entirely dependent on the quality of context you extract from the page. Sending raw innerHTML to an LLM is a waste of tokens and produces poor results. You need to extract the semantic content: the main article text, the visible form fields, the product information, the key navigation structure.
Key insight: Use Readability.js (the same library Firefox uses for Reader Mode) to extract article content from any page. It removes navigation, ads, and boilerplate, leaving only the main content — which is exactly what you want to send to the LLM.
// content.js — Page context extraction
function extractPageContext() {
const context = {
url: window.location.href,
title: document.title,
selectedText: window.getSelection()?.toString() || "",
visibleText: "",
metaDescription: "",
headings: [],
};
// Extract meta description
const metaDesc = document.querySelector("meta[name='description']");
if (metaDesc) {
context.metaDescription = metaDesc.getAttribute("content") || "";
}
// Extract headings for document structure
const headings = document.querySelectorAll("h1, h2, h3");
context.headings = Array.from(headings)
.slice(0, 10)
.map((h) => ({ level: h.tagName, text: h.textContent?.trim() || "" }));
// Extract main content using heuristics
const article = document.querySelector(
"article, main, [role='main'], .content, .post-content"
);
const source = article || document.body;
// Remove script and style elements
const clone = source.cloneNode(true) as HTMLElement;
clone.querySelectorAll("script, style, nav, header, footer, aside").forEach(
(el) => el.remove()
);
context.visibleText = clone.textContent
?.replace(/s+/g, " ")
.trim()
.slice(0, 8000) || "";
return context;
}
// Listen for messages from service worker
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.type === "GET_PAGE_CONTEXT") {
sendResponse(extractPageContext());
}
return true;
});
Running AI Calls in the Service Worker
The service worker is the right place for API calls — it runs in a non-DOM context, has no CORS restrictions for host_permissions URLs, and centralizes your API key management. The key challenge is the service worker lifecycle: it can be terminated between API calls, so you cannot rely on in-memory state persisting.
// background.js — Service worker with AI integration
import { OpenAI } from "openai";
let client = null;
async function getClient() {
if (!client) {
const { openaiApiKey } = await chrome.storage.local.get("openaiApiKey");
if (!openaiApiKey) throw new Error("No API key configured");
client = new OpenAI({ apiKey: openaiApiKey, dangerouslyAllowBrowser: true });
}
return client;
}
async function analyzePageWithAI(pageContext, userQuery) {
const openai = await getClient();
const systemPrompt = `You are an intelligent web page assistant.
The user is viewing: ${pageContext.title} (${pageContext.url})
Page content summary:
${pageContext.visibleText.slice(0, 4000)}
Answer the user's question based on this page content.
Be concise and specific. If the answer is not in the page content, say so clearly.`;
const stream = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userQuery },
],
stream: true,
max_tokens: 1024,
});
let fullResponse = "";
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content || "";
fullResponse += delta;
// Stream chunks to the side panel
chrome.runtime.sendMessage({
type: "AI_STREAM_CHUNK",
delta,
done: false,
});
}
chrome.runtime.sendMessage({ type: "AI_STREAM_CHUNK", delta: "", done: true });
return fullResponse;
}
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "ANALYZE_PAGE") {
// Get page context from content script first
chrome.tabs.query({ active: true, currentWindow: true }, async ([tab]) => {
const pageContext = await chrome.tabs.sendMessage(tab.id, {
type: "GET_PAGE_CONTEXT",
});
await analyzePageWithAI(pageContext, message.query);
});
sendResponse({ status: "processing" });
}
return true;
});
Building the Side Panel UI
The side panel (introduced in Chrome 114) is the best UI surface for AI assistants. Unlike popups, it stays open while the user browses, maintains conversation state, and has enough vertical space for meaningful responses. The chrome.sidePanel API lets you open and close it programmatically from the service worker or popup.
Design the side panel as a simple chat interface: a scrollable message list, an input field pinned to the bottom, and a streaming response display. Render streamed text incrementally — do not wait for the full response before showing anything. Users who see immediate feedback feel the response is fast even when the full generation takes 5-10 seconds.
Chrome Web Store Submission Tips
The review process for AI extensions has tightened significantly. Reviewers look for two things: clear disclosure of all host_permissions and a convincing privacy policy that specifically addresses what AI API calls are made and what data is sent to third-party services (OpenAI, in most cases). Write the privacy policy yourself, in plain English — the boilerplate generator policies consistently fail review.
Request the minimum permissions necessary. If your extension only needs to read the active tab, use activeTab instead of tabs. If you only need to store settings, use storage.local instead of requesting unlimitedStorage. Every unnecessary permission extends review time and reduces user trust at install prompts. The review process typically takes 3-7 business days for new extensions. Budget two full weeks for the first submission cycle including any revision requests.