πŸ“ Tutorials
Β· 3 min read

Build a Chrome Extension That Summarizes Any Page With AI


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

  1. Open chrome://extensions
  2. Enable β€œDeveloper mode”
  3. Click β€œLoad unpacked” β†’ select your extension folder
  4. 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+S to 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