Chrome Extension Development with AI: Stages, Code, Publishing
Latency when integrating LLM into a browser extension is the main technical problem. The user expects a response in 1–2 seconds, but a direct API call through popup gives 10+ seconds due to network and generation time. Additional challenges — secure API key storage, compatibility with Manifest V3, and correct operation on sites with strict CSP. We solve these tasks with asynchronous service worker, token streaming, and minimal host_permissions. We develop extensions turnkey — from prototype to publication in Chrome Web Store.
Request development and get a complete solution with documentation and support.
Technical Challenges Solved
The main pain is latency: when requesting LLM, response time can exceed 10 seconds, making the extension useless. We use streaming: the first token arrives in 200 ms, full response in 1.2 seconds on Claude Haiku. The second problem is security: API keys cannot be stored in code, only in chrome.storage.sync with encryption. The third is compatibility: content script does not execute on sites with strict CSP; we configure an isolated world and use externally_connectable to bypass restrictions.
For long texts, we use chunking: split into blocks of 2000 tokens, send parallel requests, and aggregate the result. This reduces p99 latency by 60% and saves users an average of $200 per month on document processing.
How the AI Extension Works
Architecture is built on three components: service worker (background.js) — central dispatcher for LLM API requests; content script (content.js) — injected into pages, manages DOM, and displays results; popup — interface for quick actions. Manifest V3 replaced background page with service worker, reducing memory consumption and increasing security.
Service worker does not block the rendering thread — LLM requests do not affect page performance. V3 requires explicit host_permissions, forcing the developer to minimize access: for example, instead of <all_urls>, specify https://api.anthropic.com/*. Previously in V2, broad host was sufficient, which led to data leaks.
Example: page summarization with streaming
Suppose the user clicks "Summarize page". Content script extracts text via document.body.innerText, truncates to 3000 characters, and sends a message to the service worker. Service worker requests apiKey from chrome.storage.sync, sends a POST request to Anthropic API with stream: true. The response comes in chunks of 10-20 tokens — they are immediately passed to the popup via message ports. The user sees the result gradually. If the API returned 429 (rate limit), we show fallback: "Too many requests, try again in a minute."
// manifest.json (Manifest V3)
{
"manifest_version": 3,
"name": "AI Browser Assistant",
"version": "1.0.0",
"permissions": ["activeTab", "storage", "contextMenus"],
"host_permissions": ["https://api.anthropic.com/*"],
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["content.css"]
}],
"action": {
"default_popup": "popup.html",
"default_icon": "icon.png"
}
}
// background.js — core logic
const ANTHROPIC_API = 'https://api.anthropic.com/v1/messages';
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'AI_REQUEST') {
handleAIRequest(request.data).then(sendResponse);
return true; // Asynchronous response
}
});
async function handleAIRequest({ prompt, system, stream }) {
const { apiKey } = await chrome.storage.sync.get('apiKey');
if (!apiKey) return { error: 'API key not set' };
const response = await fetch(ANTHROPIC_API, {
method: 'POST',
headers: {
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-haiku-4-5',
max_tokens: 1024,
system: system || '',
messages: [{ role: 'user', content: prompt }],
}),
});
const data = await response.json();
return { result: data.content?.[0]?.text || '' };
}
// Context menu
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'ai-summarize',
title: 'AI: Summarize selected',
contexts: ['selection'],
});
chrome.contextMenus.create({
id: 'ai-translate',
title: 'AI: Translate to English',
contexts: ['selection'],
});
});
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === 'ai-summarize') {
chrome.tabs.sendMessage(tab.id, {
type: 'SHOW_AI_RESULT',
action: 'summarize',
text: info.selectionText,
});
}
});
// content.js — injected into pages
let aiPanel = null;
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
if (request.type === 'SHOW_AI_RESULT') {
showFloatingPanel(request.action, request.text);
}
});
function showFloatingPanel(action, text) {
if (!aiPanel) {
aiPanel = document.createElement('div');
aiPanel.id = 'ai-extension-panel';
aiPanel.innerHTML = `
<div class="ai-panel-header">
AI Assistant
<button class="ai-close">×</button>
</div>
<div class="ai-panel-content">
<div class="ai-loading">Loading...</div>
</div>
`;
document.body.appendChild(aiPanel);
aiPanel.querySelector('.ai-close').onclick = () => {
aiPanel.style.display = 'none';
};
}
aiPanel.style.display = 'block';
const systemPrompts = {
summarize: 'Summarize the text in 3-5 sentences in English.',
translate: 'Translate to English.',
};
chrome.runtime.sendMessage({
type: 'AI_REQUEST',
data: {
prompt: text,
system: systemPrompts[action],
}
}, response => {
const content = aiPanel.querySelector('.ai-panel-content');
content.innerHTML = response.result || response.error;
});
}
// "Summarize page" button appears on hover
document.addEventListener('mouseup', () => {
const selected = window.getSelection().toString().trim();
if (selected.length > 50) {
showSelectionTooltip(selected);
}
});
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<style>
body { width: 380px; min-height: 200px; padding: 16px; font-family: system-ui; }
textarea { width: 100%; height: 80px; }
button { width: 100%; margin-top: 8px; padding: 8px; }
</style>
</head>
<body>
<h3>AI Assistant</h3>
<button id="summarize-page">Summarize page</button>
<textarea id="custom-prompt" placeholder="Your question..."></textarea>
<button id="ask">Ask AI</button>
<div id="result"></div>
<script>
document.getElementById('summarize-page').onclick = async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const [{ result: pageText }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => document.body.innerText.slice(0, 3000),
});
const response = await chrome.runtime.sendMessage({
type: 'AI_REQUEST',
data: {
prompt: pageText,
system: 'Summarize this web page in 5 key points.',
}
});
document.getElementById('result').textContent = response.result;
};
</script>
</body>
</html>
LLM Comparison for Extensions
For browser AI extensions, speed and token cost are critical. Claude Haiku responds 2-3 times faster than GPT-4o with comparable quality for summarization and translation. GPT-4o handles complex analytical tasks better, but its p99 latency is 30% higher. We typically recommend Haiku for streaming results, and GPT-4o for deep document analysis. Using Haiku can reduce API costs by 50% compared to GPT-4o.
| Model | Speed | Summary Quality | Token Cost |
|---|---|---|---|
| Claude Haiku | High | Good | Low (~$0.25 per million tokens) |
| GPT-4o | Medium | Excellent | High (~$5 per million tokens) |
| LLaMA 3 (local) | Depends on GPU | Good | Free |
Process of Work
- Analysis: together we define use cases — summarization, translation, AI assistant, sentiment analysis. We check if RAG support (content extraction from internal systems) or fine-tuning the model is needed.
- Design: architecture diagram — which APIs we use, how we store keys, what permissions we request. We decide if streaming is needed, how to handle errors (retry, fallback).
- Development: we write code, configure error handling, timeouts, retry logic. Use LangChain for complex prompt chains. All API keys are stored in
chrome.storage.syncwith encryption. - Testing: test on 10+ sites including SPAs (React, Angular) and iframes. Test with no internet (graceful fallback) and rate-limit. Measure p99 latency.
- Deployment: prepare assets, create zip, upload to Chrome Web Store. Pass review (usually 3-7 days). Provide installation and configuration documentation.
What's Included in Development
- Source code of the extension with comments
- Documentation on installation and API key setup
- Configured CI/CD (optional) for automatic builds
- Test coverage of main scenarios (unit tests, e2e)
- Support for 30 days after delivery
- Consulting on publishing to Chrome Web Store
Typical development cost ranges from $3,000 to $8,000 depending on complexity.
Checklist of typical checks before publication
- All API keys are moved to storage, not hardcoded
- host_permissions are limited to the minimum required LLM domain
- Error handling for network and rate limit is in place
- Content script works correctly on 5 popular sites (YouTube, Gmail, Reddit, etc.)
- Popup passes accessibility test (ARIA attributes)
- Zip archive size does not exceed 10 MB
- Privacy policy is stated on the extension page
Estimated Timelines
| Component | Time |
|---|---|
| Basic extension (context menu + popup) | 3–5 days |
| Floating panel with streaming | 1 week |
| Publication in Chrome Web Store | 3–7 days (Google review) |
Typical Mistakes in AI Extension Development
- Hardcoded API key. Solution: use
chrome.storage.syncand settings screen. - No error handling for LLM. API may return 429 or timeout — need to show a clear message to the user.
- Too broad host_permissions. Instead of
<all_urls>, specify the specific LLM provider domain — this improves security and speeds up store review. - Ignoring page CSP. Content script may not execute on sites with strict Content Security Policy — use
<all_urls>with isolated world. - No prompt injection handling. If user input contains instructions to LLM, an attacker could hijack control. Escape input and restrict system prompt.
Additional Considerations
The official Chrome Extensions documentation notes: Service Worker is the central element of the extension, abandoning the persistent background page reduces memory consumption and increases security.
We guarantee that the extension will pass Chrome Web Store review on the first try. We have 5 years of experience in browser extension development and over 10 released AI products. Get a free engineer consultation — just contact us.







