Claude Skills Guide

AI Summarizer Chrome Extension: Build Your Own Text Summarization Tool

Chrome extensions that use AI for text summarization have become essential tools for developers, researchers, and power users who need to quickly extract key information from lengthy web content. This guide walks you through building a functional AI summarizer extension from scratch, with practical code examples you can adapt for your own projects.

Why Build an AI Summarizer Extension

Browser-based AI summarizers offer several advantages over standalone applications. They integrate directly into your workflow, requiring no context switching between your browser and separate tools. You can summarize articles, documentation, forum threads, and emails without leaving your current tab. For developers building these extensions, the Chrome platform provides robust APIs for content extraction, storage, and user interaction.

The core challenge lies in effectively extracting page content and passing it to an AI API while maintaining performance and respecting user privacy. A well-designed extension should handle various page types, provide clear user feedback, and offer customization options.

Core Architecture

A basic AI summarizer Chrome extension consists of three main components: a content script for extracting page text, a background service for API communication, and a popup interface for user controls. This separation keeps your code modular and maintains security boundaries.

Your extension will need to request permissions for activeTab and scripting to access page content. For the AI component, you can integrate with APIs from OpenAI, Anthropic, or self-hosted models using libraries like Ollama.

Implementation Guide

Step 1: Project Structure

Create a new directory with the following structure:

ai-summarizer/
├── manifest.json
├── popup.html
├── popup.js
├── popup.css
├── content.js
├── background.js
└── options.html

Step 2: Manifest Configuration

Your manifest.json defines the extension’s capabilities and permissions:

{
  "manifest_version": 3,
  "name": "AI Summarizer",
  "version": "1.0",
  "description": "AI-powered text summarization for any web page",
  "permissions": ["activeTab", "scripting", "storage"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "background": {
    "service_worker": "background.js"
  },
  "host_permissions": ["<all_urls>"]
}

Step 3: Content Extraction Script

The content script extracts readable text from the current page. This script uses a sophisticated approach to find the main content while avoiding navigation elements, ads, and other non-essential material:

// content.js
function extractPageContent() {
  // Try common content selectors first
  const selectors = [
    'article',
    '[role="main"]',
    'main',
    '.post-content',
    '.article-content',
    '.entry-content',
    '#content'
  ];

  for (const selector of selectors) {
    const element = document.querySelector(selector);
    if (element && element.textContent.length > 500) {
      return cleanText(element.textContent);
    }
  }

  // Fallback: find the largest text block by scanning paragraph parent elements
  const paragraphs = document.querySelectorAll('p');
  let maxText = '';
  let parentElement = null;

  paragraphs.forEach(p => {
    const parent = p.parentElement;
    const text = parent.textContent;
    if (text.length > maxText.length) {
      maxText = text;
      parentElement = parent;
    }
  });

  if (parentElement) {
    return cleanText(parentElement.textContent);
  }

  // Last resort: extract from body, removing common non-content elements
  const clone = document.body.cloneNode(true);
  const removeSelectors = ['script', 'style', 'nav', 'header', 'footer',
                            'aside', '.ad', '.sidebar', '.comments'];

  removeSelectors.forEach(sel => {
    clone.querySelectorAll(sel).forEach(el => el.remove());
  });

  return cleanText(clone.textContent);
}

function cleanText(text) {
  return text
    .replace(/\s+/g, ' ')
    .replace(/\n+/g, '\n')
    .trim()
    .slice(0, 15000); // API token limits
}

// Listen for messages from popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'getContent') {
    const content = extractPageContent();
    sendResponse({ content });
  }
});

Step 4: Popup Interface

The popup provides the user interface for triggering summarization:

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="container">
    <h2>AI Summarizer</h2>
    <div class="options">
      <label>
        <input type="radio" name="length" value="short" checked>
        Short Summary
      </label>
      <label>
        <input type="radio" name="length" value="medium">
        Medium Summary
      </label>
      <label>
        <input type="radio" name="length" value="long">
        Detailed Summary
      </label>
    </div>
    <button id="summarizeBtn">Summarize This Page</button>
    <div id="result" class="result hidden"></div>
    <div id="loading" class="hidden">
      <span class="spinner"></span> Analyzing content...
    </div>
  </div>
  <script src="popup.js"></script>
</body>
</html>

Step 5: Popup Logic and API Integration

The popup script handles user interactions and communicates with your AI API:

// popup.js
document.getElementById('summarizeBtn').addEventListener('click', async () => {
  const btn = document.getElementById('summarizeBtn');
  const result = document.getElementById('result');
  const loading = document.getElementById('loading');
  
  // Get selected summary length
  const length = document.querySelector('input[name="length"]:checked').value;
  
  btn.disabled = true;
  loading.classList.remove('hidden');
  result.classList.add('hidden');

  try {
    // Extract page content
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    
    const contentResponse = await chrome.tabs.sendMessage(tab.id, {
      action: 'getContent'
    });
    
    if (!contentResponse.content) {
      throw new Error('Could not extract page content');
    }

    // Call your AI API
    const summary = await callAIApi(contentResponse.content, length);
    
    result.textContent = summary;
    result.classList.remove('hidden');
  } catch (error) {
    result.textContent = `Error: ${error.message}`;
    result.classList.remove('hidden');
  } finally {
    btn.disabled = false;
    loading.classList.add('hidden');
  }
});

async function callAIApi(content, length) {
  const apiKey = await getApiKey(); // Retrieve from storage
  const lengthPrompts = {
    short: 'Provide a 2-3 sentence summary.',
    medium: 'Provide a concise paragraph summary.',
    long: 'Provide a detailed summary with key points.'
  };

  const response = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': apiKey,
      'anthropic-version': '2023-06-01'
    },
    body: JSON.stringify({
      model: 'claude-3-haiku-20240307',
      max_tokens: 1024,
      messages: [{
        role: 'user',
        content: `${lengthPrompts[length]}\n\nText to summarize:\n\n${content}`
      }]
    })
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  const data = await response.json();
  return data.content[0].text;
}

async function getApiKey() {
  const result = await chrome.storage.sync.get(['apiKey']);
  if (!result.apiKey) {
    throw new Error('API key not configured. Please set it in options.');
  }
  return result.apiKey;
}

Alternative: OpenAI Integration with Background Script Routing

If you prefer OpenAI or want to support multiple summarization styles (bullet points vs. paragraphs), move the API call into background.js and have the popup route requests through it. This pattern also keeps your API key out of the popup context entirely.

// background.js - OpenAI summarization handler
const API_ENDPOINT = 'https://api.openai.com/v1/chat/completions';

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'generateSummary') {
    generateSummary(request.content, request.style)
      .then(sendResponse)
      .catch(err => sendResponse({ error: err.message }));
    return true; // keep channel open for async response
  }
});

async function generateSummary(content, style = 'concise') {
  const apiKey = await getApiKey();
  if (!apiKey) {
    throw new Error('API key not configured. Please set your API key in extension settings.');
  }

  const systemPrompt = style === 'bullet'
    ? 'Summarize the following article in 5-7 bullet points. Each point should capture a key concept or finding.'
    : 'Summarize the following article in 2-3 paragraphs. Capture the main points and key takeaways.';

  const response = await fetch(API_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`
    },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages: [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: `Article content:\n\n${content}` }
      ],
      temperature: 0.5,
      max_tokens: 1000
    })
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  const data = await response.json();
  return data.choices[0].message.content;
}

async function getApiKey() {
  const result = await chrome.storage.local.get('openaiApiKey');
  return result.openaiApiKey;
}

When using this routing pattern, the popup sends a message to background rather than calling the API directly:

// In popup.js — send to background for processing
const summary = await chrome.runtime.sendMessage({
  action: 'generateSummary',
  content: response.content,
  style: styleSelect.value  // 'concise' or 'bullet'
});

You can expose the style choice in the popup with a <select> element offering “Paragraph Summary” and “Bullet Points” options. This gives users flexible output formats without duplicating API logic across files.

Step 6: Options Page for API Key Management

Users need a secure way to store their API keys:

<!-- options.html -->
<!DOCTYPE html>
<html>
<body>
  <h1>AI Summarizer Settings</h1>
  <label>
    Anthropic API Key:
    <input type="password" id="apiKey" placeholder="sk-ant-...">
  </label>
  <button id="saveBtn">Save</button>
  <p id="status"></p>
  
  <script>
    document.getElementById('saveBtn').addEventListener('click', async () => {
      const apiKey = document.getElementById('apiKey').value;
      await chrome.storage.sync.set({ apiKey });
      document.getElementById('status').textContent = 'Saved!';
    });
    
    chrome.storage.sync.get(['apiKey'], (result) => {
      if (result.apiKey) {
        document.getElementById('apiKey').value = result.apiKey;
      }
    });
  </script>
</body>
</html>

Advanced Considerations

For production extensions, consider implementing caching to avoid redundant API calls for the same content. You can use Chrome’s storage API to cache summaries keyed by URL. Rate limiting prevents API quota exhaustion during heavy use. Error handling should gracefully manage API failures, network issues, and content extraction problems.

Security best practices include never hardcoding API keys, using Chrome’s secure storage mechanisms, and implementing proper Content Security Policy headers in your manifest.

Building an AI summarizer Chrome extension gives you a powerful tool tailored to your specific needs while demonstrating practical integration of browser APIs with external AI services. The modular architecture allows you to swap AI providers, add new features, and customize the experience based on your workflow requirements.

Built by theluckystrike — More at zovo.one