onezlabs

0%

Browser Extensions

Chrome Extension Development with AI Integration in 2026

How to build Chrome extensions with real AI features — not just GPT wrappers. Covers Manifest V3, content scripts, side panel, and LLM integration patterns.

April 22, 20269 min read
#Chrome Extension#AI#JavaScript#OpenAI#Browser API
Chrome Extension Development with AI Integration in 2026

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.

Frequently Asked Questions

Is Manifest V3 stable enough for production extensions in 2026?

Yes. Google completed the MV2 deprecation in June 2024. All new extensions must use Manifest V3, and all existing MV2 extensions have been migrated or removed from the Chrome Web Store. The main differences that affect AI extensions: service workers replace background pages (no persistent state between activations), declarativeNetRequest replaces webRequest for blocking (irrelevant for most AI extensions), and remote code execution is prohibited (you cannot eval() or load remote scripts).

How do I handle API keys securely in a Chrome extension?

Never ship your OpenAI API key in the extension bundle — anyone who installs the extension can extract it from the source files. The correct pattern is a backend proxy: your extension calls your own API endpoint (FastAPI, Vercel, Cloudflare Workers), which holds the API key server-side and calls OpenAI. For extensions where users supply their own API key, store it in chrome.storage.local (encrypted by Chrome) and call OpenAI directly from the service worker, never from content scripts.

Why does my AI call time out in a service worker?

Chrome MV3 service workers have a 5-minute idle timeout. An AI API call that takes 30 seconds will usually complete, but if the service worker was already idle and needs to restart, the call can fail. The fix is to use chrome.alarms.create() to keep the service worker alive during long operations, or to move the AI call to an offscreen document which has a different lifecycle. For streaming responses, use the Streams API and process chunks as they arrive rather than awaiting the full response.

Share:TwitterLinkedIn

Get the newsletter

Practical articles on AI development, full stack engineering, and WordPress — delivered whenI publish, not on a schedule. No spam, ever.

JO

Johnbert Oñez

AI Solutions Engineer & Full Stack Developer

Johnbert builds AI systems, web applications, and WordPress solutions for clients worldwide. Based in Davao City, Philippines. 6+ years, 50+ projects.

More Like This

Enjoyed the article? Let's build something together.

From AI chatbots to SaaS products — I turn ideas into working software. Based in Davao City, available worldwide.