Chrome Extension Link Checker — Developer Guide

5 min read

Build a Link Checker Extension

What You’ll Build

A Chrome extension that scans pages for broken links, checks HTTP status, highlights results, and generates reports.

Manifest Configuration

{
  "permissions": ["activeTab", "scripting"],
  "host_permissions": ["<all_urls>"],
  "action": { "default_popup": "popup.html" }
}

Content script to get all page links:

// content.js
function extractLinks() {
  const anchors = document.querySelectorAll('a[href]');
  return Array.from(anchors).map(a => ({
    href: a.href,
    text: a.textContent.trim().substring(0, 50)
  }));
}

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'extractLinks') {
    sendResponse({ links: extractLinks() });
  }
  return true;
});

Background script handles HTTP requests (avoids CORS):

// background.js
const cache = new Map();
const CONCURRENT_LIMIT = 5;

async function checkLink(url) {
  if (cache.has(url)) return cache.get(url);
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 10000);
  
  try {
    const response = await fetch(url, { 
      method: 'HEAD',
      signal: controller.signal,
      redirect: 'follow'
    });
    clearTimeout(timeoutId);
    const result = { 
      status: response.status, 
      ok: response.ok,
      redirect: response.redirected
    };
    cache.set(url, result);
    return result;
  } catch (error) {
    clearTimeout(timeoutId);
    const isCors = error.message.includes('Failed to fetch');
    cache.set(url, { 
      status: 0, 
      ok: false, 
      error: error.name === 'AbortError' ? 'timeout' : (isCors ? 'cors-blocked' : error.message) 
    });
    return cache.get(url);
  }
}

async function checkBatch(links, onProgress) {
  const results = [];
  for (let i = 0; i < links.length; i += CONCURRENT_LIMIT) {
    const batch = links.slice(i, i + CONCURRENT_LIMIT);
    const batchResults = await Promise.all(batch.map(l => checkLink(l.href)));
    results.push(...batch.map((r, idx) => ({ ...links[i + idx], ...batchResults[idx] })));
    onProgress({ checked: results.length, total: links.length });
  }
  return results;
}

Step 3: Visual Highlighting

// content.js
function highlightLinks(results) {
  results.forEach(link => {
    const element = document.querySelector(`a[href="${link.href}"]`);
    if (!element) return;
    element.style.outline = link.ok ? '2px solid #22c55e' : '2px solid #ef4444';
  });
}

Step 4: Popup Report

<style>.broken { color: #ef4444; }</style>
<div id="results"></div>
<button id="copyReport">Copy Report</button>
// popup.js
document.getElementById('copyReport').addEventListener('click', () => {
  const broken = results.filter(r => !r.ok);
  navigator.clipboard.writeText(broken.map(l => `${l.status}: ${l.href}`).join('\n'));
});

// Badge showing broken link count
chrome.runtime.sendMessage({
  action: 'updateBadge',
  count: results.filter(r => !r.ok).length
});

Step 5: Badge Action

Display broken link count in extension badge:

// background.js
chrome.runtime.onMessage.addListener((message) => {
  if (message.action === 'updateBadge') {
    chrome.action.setBadgeText({ text: message.count > 0 ? String(message.count) : '' });
    chrome.action.setBadgeBackgroundColor({ color: message.count > 0 ? '#ef4444' : '#22c55e' });
  }
});

Summary

Extension demonstrates link extraction, HTTP checking with rate limiting, visual highlighting, and report generation.

See Also

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article