How to build a Chrome extension with AI (Manifest V3)

Build a Chrome extension with AI, step by step: Manifest V3, content scripts, service worker, minimal permissions, hiding API keys and Chrome Web Store publishing.

Solviera Teknoloji 8 min read Türkçe oku

Building a Chrome extension with AI is one of the easiest ways into vibe coding: an extension is a few small files, you test it in your own browser without any server, and coding agents like Claude Code or Codex know the Manifest V3 structure well. The hard part isn’t the code; it’s picking the right piece (content script, service worker, popup), asking for the fewest permissions, and never putting API keys inside the extension. This post covers the path I follow from an empty folder to the Chrome Web Store.

As an example we’ll build something small but real: an extension that summarizes the article on the current page with an AI API. With vibe coding you can have a first version in an afternoon; the version you publish needs a few more rules.

Know the pieces first

To tell the agent what you want, you only need to know the four building blocks of a Chrome extension:

  • manifest.json: The extension’s identity. Name, version, which file does what and which permissions it asks for. New extensions use Manifest V3.
  • Content script: JavaScript that runs inside the page you visit. It can read the page’s text, add a button, hide an element. It can’t reach the page’s own JavaScript variables directly; it runs in an isolated “world”.
  • Service worker (background): Code that runs independently of any page. It reacts to events like a click on the toolbar icon, tab changes or alarms, and it makes network requests. One important property: Chrome shuts it down when it’s idle, so anything kept in a global variable can disappear.
  • Popup and options page: The small window that opens when you click the icon, and a separate page for settings. Both are plain HTML pages.

These pieces talk to each other with chrome.runtime.sendMessage and chrome.tabs.sendMessage. When something the agent wrote “doesn’t work”, the cause is very often code running in the wrong piece, for example trying to use document in the service worker.

1. Setup and project structure

What you need: Chrome (or another Chromium browser), Git, a code editor and a coding agent. A build tool isn’t required for a first extension; plain JavaScript is enough. If the project grows, Vite-based extension frameworks like WXT add TypeScript and auto-reload, but I’d skip the extra layer for version one. The agent’s mistakes are easier to see without it.

mkdir page-summarizer && cd page-summarizer
git init

Then put a short rules file at the project root that the agent reads every session (CLAUDE.md or AGENTS.md):

# Page Summarizer (Chrome extension)
- Manifest V3, plain JavaScript, no build step.
- Minimal permissions: activeTab, scripting, storage. No <all_urls>.
- No remote code; no eval, no remote <script>.
- No API key in the extension; requests go to our own server.
- Persistent state in chrome.storage, not in service worker globals.
- Ask before adding a permission or a package.

2. The first prompt

In the first message I ask the agent to explain what it will build and which files it will create, then write the code:

Read CLAUDE.md. Build a Manifest V3 Chrome extension:
- Clicking the toolbar icon opens a popup with a "Summarize" button.
- Pressing it grabs the article text from the active tab (use activeTab +
  scripting and inject the content script only at that moment).
- The text goes to the service worker, which POSTs it to
  https://api.my-server.example/summarize.
- Show the summary in the popup; on error show a readable message.
- Keep the last 10 summaries in chrome.storage.local.
First list the files and what each one does, then create them.
Finish with step-by-step instructions to load and test it.

The manifest.json the agent produces should look roughly like this:

{
  "manifest_version": 3,
  "name": "Page Summarizer",
  "version": "0.1.0",
  "description": "Summarizes the article on the current page in one click.",
  "action": { "default_popup": "popup.html" },
  "background": { "service_worker": "background.js" },
  "permissions": ["activeTab", "scripting", "storage"],
  "host_permissions": ["https://api.my-server.example/*"]
}

The thing I check here is the permission list. activeTab grants temporary access to just the current tab when the user clicks the icon. Agents often add "<all_urls>" or "tabs" for convenience; that shows users a scary warning and slows down store review.

3. Load, test, debug

Before going anywhere near the store, test the extension in your own browser:

  1. Open chrome://extensions.
  2. Turn on Developer mode in the top right.
  3. Click Load unpacked and pick the project folder.
  4. Pin the icon to the toolbar and try it on an article.

After you change code, press the reload button on the extension card at chrome://extensions. If a content script changed, reload the page you’re testing on as well.

The three places that help most when debugging:

  • Service worker: The “service worker” link on the extension card opens its own DevTools. Network requests and background errors show up here.
  • Popup: With the popup open, right-click it and choose Inspect.
  • Content script: Logs appear in the page’s own DevTools console; you can pick your extension from the context dropdown at the top of the console.

If the extension card shows an Errors button, copy its contents as is and give them to the agent:

I loaded the extension. Pressing "Summarize" in the popup shows this error:
[error text]
The service worker console also shows:
[log]
Explain the cause, fix it, and list what you changed in which file.

Saying where it broke, what you clicked and what each console shows lets the agent fix the problem instead of guessing.

4. Storage: pick the right place

  • chrome.storage.local: Data on this device, like the summary history.
  • chrome.storage.sync: Synced across devices through the user’s Chrome profile. The quota is small; use it only for settings.
  • chrome.storage.session: Cleared when the browser closes; good for temporary state that has to survive the service worker restarting.

There’s no localStorage in a service worker. If the agent tries to use it, that’s why it fails. And don’t forget the storage permission in manifest.json.

5. Keep API keys out of the extension

This is the mistake I see most in AI-built extensions. The agent writes “paste your OpenAI key here” and puts a constant in background.js. A published extension is a ZIP file; anyone can download it, unpack it and read your key. Minifying or obfuscating doesn’t change that.

The right structure is a small proxy:

  1. The extension calls your own server (https://api.my-server.example/summarize).
  2. The server keeps the key in its environment and calls the real AI API.
  3. The server rate-limits requests (per user or IP) and rejects oversized text.

Open a separate project for this server and tell the agent:

Write a small HTTP service: POST /summarize takes { text } in the body.
Cap the text at 20,000 characters, call the AI API with the key from an
environment variable, return { summary }. Allow CORS only for the
chrome-extension://<EXTENSION_ID> origin. Add a simple rate limit.
Never log the key. Create a .env.example file.

The extension ID is shown on chrome://extensions when you load it unpacked; once published in the store it may be different. Update the CORS setting before you publish. If users bring their own key (common for developer tools), ask for it on the options page and keep it in chrome.storage.local; never ship your own.

What the AI gets wrong

  • Writing Manifest V2 code. background.page, chrome.browserAction, a persistent background page or webRequestBlocking come from old examples. Tell the agent “use the Manifest V3 equivalent.”
  • Assuming the service worker is always running. A counter in a global variable or a setInterval stops after a while. Use chrome.alarms for scheduled work and chrome.storage for state.
  • Asking for too many permissions. <all_urls>, tabs, history are usually unnecessary. Ask the agent to justify each permission in one sentence; drop the ones it can’t justify.
  • Loading remote code. Pulling a library from a CDN with <script> or using eval isn’t allowed in Manifest V3. Bundle the library into the package.
  • Reaching for page variables from a content script. Because it runs in an isolated world it can’t see window.someApp; it has to work through the DOM.
  • Forgetting async replies in messaging. If an onMessage listener responds asynchronously it has to return true, otherwise the popup gets an empty response.
  • Assuming every site has the same HTML. To grab article text, look for an article or main element first and fall back sensibly. Test on three different sites.

Security

  • Secrets live in .env, on the server. No .env in the extension folder; it would end up in the ZIP.
  • Never paste API keys into the chat. The agent needs the variable name, not the key.
  • Read the commands the agent runs. Especially package installs and anything like curl … | sh.
  • Don’t trust page data. Don’t render text the content script read with innerHTML in the popup; use textContent. Otherwise a malicious page can inject code into your extension.
  • Check who a message comes from. If you don’t need messages from outside, don’t add externally_connectable; if you do, keep the allowed origins narrow.
  • Collect only what you need. If page text goes to your server, say so clearly and don’t store it there.

6. Publishing on the Chrome Web Store

Roughly, publishing goes like this:

  1. Create a developer account. Register in the Chrome Web Store developer dashboard; there’s a one-time registration fee.
  2. Package it. ZIP the project folder with the manifest at the root. Leave out .git, node_modules, test files and .env.
  3. Fill in the listing. Description, screenshots, icons, category.
  4. Fill in the privacy section. State the extension’s single purpose, justify every permission and declare what user data you collect. If you collect data, you need a publicly reachable privacy policy URL.
  5. Submit for review. The time varies. Broad host permissions and hard-to-read code can slow it down; if you’re rejected, the email says which policy you hit.

The agent helps here too:

I'm submitting this extension to the Chrome Web Store. For each
permission in manifest.json, write a one-sentence justification for the
store form, write the single-purpose description and list the user data
we collect. Tell me if any permission looks unnecessary. Give me a command
that builds the release ZIP without .git, .env and test files.

For updates, bump version in manifest.json. Adding a new permission can disable the extension for existing users until they approve it, so think twice before adding one.

How I do this with AgentVera

You can build an extension with one terminal and a browser. I use AgentVera when I’m building the extension and the proxy server at the same time:

  • Two agents, two jobs. In the workspace I open a Claude Code agent for the extension and a Codex agent for the proxy server, each in its own folder or git worktree.
  • Trying the server. The API client can find the server’s endpoints from its code; I try /summarize there before wiring it to the extension.
  • Sending errors to the agent. I send error output from a terminal to the chosen agent with Send to agent instead of copy-pasting.
  • Review before merging. Code review checks every finished turn, and the Git panel shows the diff. An extra permission or an embedded key usually gets caught here.
  • Token tracking. Each pane shows live context size; a small extension doesn’t need a bloated session.

Loading the extension into Chrome and trying it is still on you; AgentVera doesn’t do that for you.

Wrapping up

To build a Chrome extension with AI, learn the pieces (manifest, content script, service worker, popup), give the agent written rules, start with minimal permissions, load it unpacked and debug from the right console, and keep API keys behind a proxy server. Before submitting to the store, make sure you can justify every permission. For more on checking what agents produce, read AI code review, and for picking tools, the best AI coding CLI tools.

Questions

Can I build a Chrome extension with AI without knowing how to code?

A simple extension, yes; most extensions are a handful of small files a coding agent can write. Learning the basics, such as the manifest, permissions and how the parts message each other, still helps you describe bugs to the agent accurately.

What is the difference between a content script and a service worker?

A content script runs inside the page you visit and can read and change its DOM. The service worker runs in the background, independent of any page; it reacts to events, makes network requests and is shut down by Chrome when idle.

How do I hide an API key in a Chrome extension?

You can’t; anything you ship inside an extension can be read by the user. Keep the key on your own server, have the extension call your server, and let the server call the real API.

How long does Chrome Web Store review take?

There’s no fixed time; it can take from a few hours to several days, sometimes longer. Extensions that request broad permissions or ship hard-to-read minified code tend to get a more detailed review.

Can AI convert a Manifest V2 extension to Manifest V3?

Yes, agents handle this migration well: background page to service worker, remote code into the package, persistent variables into chrome.storage. Test every feature by hand afterwards, because the service worker shutting down changes behavior.

Bring your agents to one desk.

Download AgentVera for free; your installed CLIs are ready to go.

More posts