You open a 3,000-word article. You need the key points, not the fluff. You could read the whole thing, or you could click one button and get a summary in 10 seconds.
In this tutorial, weβll build a Chrome extension that extracts the main content from any webpage and generates an AI summary in a sidebar panel.
What weβre building
Click the extension icon on any page β a sidebar opens with:
- A 3-sentence TL;DR
- Key points as bullet points
- Reading time of the original article
Project structure
summarizer-extension/
βββ manifest.json
βββ background.js
βββ sidebar.html
βββ sidebar.js
βββ content.js
Step 1: manifest.json
{
"manifest_version": 3,
"name": "Page Summarizer",
"version": "1.0",
"description": "Summarize any webpage with AI",
"permissions": ["activeTab", "sidePanel", "storage"],
"background": { "service_worker": "background.js" },
"side_panel": { "default_path": "sidebar.html" },
"action": { "default_title": "Summarize this page" },
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"]
}]
}
Step 2: Content script (extracts page text)
// content.js β Runs on every page, extracts main content
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getContent') {
// Try to get the main article content
const article = document.querySelector('article') ||
document.querySelector('[role="main"]') ||
document.querySelector('main') ||
document.body;
// Remove scripts, styles, nav, footer
const clone = article.cloneNode(true);
clone.querySelectorAll('script, style, nav, footer, header, aside').forEach(el => el.remove());
const text = clone.textContent.replace(/\s+/g, ' ').trim();
const wordCount = text.split(/\s+/).length;
sendResponse({
text: text.slice(0, 10000), // Limit to ~10K chars
wordCount,
title: document.title,
url: window.location.href
});
}
return true;
});
Step 3: Background script (opens sidebar)
// background.js
chrome.action.onClicked.addListener(async (tab) => {
await chrome.sidePanel.open({ tabId: tab.id });
// Small delay to ensure sidebar is ready
setTimeout(() => {
chrome.runtime.sendMessage({ action: 'summarize', tabId: tab.id });
}, 500);
});
Step 4: Sidebar
<!-- sidebar.html -->
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui; padding: 1em; font-size: 14px; line-height: 1.6; width: 350px; }
h2 { font-size: 16px; margin-top: 1.5em; }
.meta { color: #666; font-size: 12px; margin-bottom: 1em; }
.loading { color: #666; }
#summary { white-space: pre-wrap; }
.error { color: #dc2626; }
</style>
</head>
<body>
<h1>π Summary</h1>
<div id="summary"><p class="loading">Click the extension icon on any page to summarize it.</p></div>
<script src="sidebar.js"></script>
</body>
</html>
// sidebar.js
const ANTHROPIC_API_KEY = ''; // User sets this in extension options
chrome.runtime.onMessage.addListener(async (request) => {
if (request.action !== 'summarize') return;
const summary = document.getElementById('summary');
summary.innerHTML = '<p class="loading">β³ Reading page...</p>';
// Get page content from content script
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.tabs.sendMessage(tab.id, { action: 'getContent' }, async (response) => {
if (!response?.text) {
summary.innerHTML = '<p class="error">Could not extract page content.</p>';
return;
}
summary.innerHTML = `<p class="meta">${response.wordCount} words Β· ~${Math.ceil(response.wordCount / 200)} min read</p><p class="loading">β³ Generating summary...</p>`;
try {
const res = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': ANTHROPIC_API_KEY,
'anthropic-version': '2023-06-01',
'anthropic-dangerous-direct-browser-access': 'true',
},
body: JSON.stringify({
model: 'claude-haiku-3.5',
max_tokens: 500,
messages: [{
role: 'user',
content: `Summarize this webpage content:
Title: ${response.title}
Content:
${response.text}
Format:
## TL;DR
[3 sentences max]
## Key Points
[5-7 bullet points covering the main ideas]
Be concise. Skip filler and repetition.`
}],
}),
});
const data = await res.json();
summary.textContent = data.content[0].text;
} catch (error) {
summary.innerHTML = `<p class="error">Error: ${error.message}</p>`;
}
});
});
Step 5: Install and test
- Open
chrome://extensions - Enable βDeveloper modeβ
- Click βLoad unpackedβ β select your extension folder
- Navigate to any article and click the extension icon
Making it better
- Options page: Let users set their API key through an options page instead of hardcoding
- Cache summaries: Store summaries by URL so you donβt re-summarize pages youβve already read
- Keyboard shortcut: Add
Ctrl+Shift+Sto trigger summarization - Multiple formats: Add buttons for βTL;DR only,β βKey points,β or βFull summaryβ
- Use Haiku: Claude Haiku is 10x cheaper than Sonnet and fast enough for summaries
Total build time: ~45 minutes. API cost: ~$0.001 per summary with Haiku.
Related: Best Free AI APIs 2026 Β· How to Build an AI Agent Β· Ollama Complete Guide