Claude Skills Guide

Google’s transition from Manifest V2 to Manifest V3 represents the most significant API overhaul for Chrome extensions since the platform’s inception. This guide walks you through the practical migration steps, highlighting the key differences and providing working code examples you can apply immediately.

Understanding the Manifest V3 Timeline

Google first announced the deprecation of Manifest V2 in 2020, and the timeline has shifted multiple times due to developer feedback. As of early 2026, all Chrome Web Store submissions require Manifest V3, and existing V2 extensions receive limited support. If you maintain any extension, migration is no longer optional—it’s a requirement for continued distribution.

The core motivations behind Manifest V3 center on security, performance, and user privacy. Background pages in V2 could run continuously, consuming memory and creating potential attack surfaces. Network requests were harder to audit. Manifest V3 addresses these concerns through mandatory service workers, declarative net request rules, and tighter permission controls.

Key Breaking Changes You Need to Know

Several fundamental changes affect almost every extension during migration:

Background Pages Become Service Workers

The most significant architectural shift replaces persistent background pages with ephemeral service workers. Service workers in Manifest V3 are event-driven, short-lived processes that terminate after handling events. This impacts how you store state, manage timers, and handle asynchronous operations.

// Manifest V2 background page (deprecated)
chrome.runtime.onInstalled.addListener(() => {
  console.log('Extension installed');
  // Persistent background page stays alive
});

// Manifest V3 service worker
chrome.runtime.onInstalled.addListener(() => {
  console.log('Extension installed');
  // Service worker terminates after this event
});

For state management, you now rely on chrome.storage or external storage solutions rather than in-memory variables. Timer handling requires special consideration since setTimeout and setInterval don’t persist across service worker restarts.

Declarative Net Request Replaces Web Request

Blocking and modifying network requests in V2 used the webRequest API with blocking permissions. Manifest V3 replaces this with declarativeNetRequest, which shifts request modification to the extension manifest as declarative rules.

// manifest.json (Manifest V3)
{
  "name": "My Extension",
  "version": "1.0",
  "manifest_version": 3,
  "permissions": ["declarativeNetRequest"],
  "host_permissions": ["*://*.example.com/*"],
  "declarative_net_request": {
    "rule_resources": [{
      "id": "ruleset_1",
      "enabled": true,
      "path": "rules.json"
    }]
  }
}
// rules.json
[
  {
    "id": 1,
    "priority": 1,
    "action": {
      "type": "block"
    },
    "condition": {
      "urlFilter": "*.ads.example.com",
      "resourceTypes": ["image", "script"]
    }
  }
]

This approach improves security by preventing extensions from observing raw network traffic while still enabling content blocking.

Action API Consolidation

V2 used browserAction and pageAction APIs separately. V3 consolidates these into a single action API:

// Manifest V2
browserAction: {
  "default_popup": "popup.html",
  "default_icon": "icon.png"
}

// Manifest V3
action: {
  "default_popup": "popup.html",
  "default_icon": {
    "16": "images/icon16.png",
    "48": "images/icon48.png",
    "128": "images/icon128.png"
  }
}
// JavaScript calls
// V2: chrome.browserAction.setBadgeText()
// V3: chrome.action.setBadgeText()
chrome.action.setBadgeText({ text: '5' });
chrome.action.setBadgeBackgroundColor({ color: '#FF0000' });

Migrating Content Scripts

Content scripts remain available in Manifest V3 but require different injection approaches. The document_body injection mode replaces the old automatic injection, giving you more control over when scripts execute.

// manifest.json content script configuration
{
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"],
    "run_at": "document_idle"
  }]
}

Dynamic injection through chrome.scripting.executeScript provides more flexibility for extensions that need conditional script loading:

// Manifest V3 dynamic injection
chrome.scripting.executeScript({
  target: { tabId: tabId },
  files: ['content.js']
}).then(() => {
  consoleScript(tabId);
});

function consoleScript(tabId) {
  chrome.scripting.executeScript({
    target: { tabId: tabId },
    func: () => console.log('Content script loaded')
  });
}

Handling Asynchronous Patterns

Service worker lifecycle creates new challenges for asynchronous operations. Chrome provides several patterns to handle these constraints:

// Use chrome.alarms for recurring tasks
chrome.alarms.create('periodicSync', {
  periodInMinutes: 15
});

chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'periodicSync') {
    // Handle periodic task
    doSyncWork();
  }
});

// Use message passing for communication
// From popup to service worker
chrome.runtime.sendMessage({ type: 'FETCH_DATA' }, (response) => {
  console.log(response.data);
});

// In service worker
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'FETCH_DATA') {
    fetchData().then(data => sendResponse({ data }));
    return true; // Keep message channel open for async response
  }
});

Permission Changes and Manifest V3 Migration

Several permissions behave differently or require explicit declaration:

// manifest.json - permissions section
{
  "permissions": [
    "storage",
    "alarms",
    "scripting",
    "declarativeNetRequest"
  ],
  "host_permissions": [
    "https://api.example.com/*"
  ]
}

The host_permissions key separates website access from core extension capabilities. Extensions requiring broad host access must declare these explicitly, and Chrome displays these permissions more prominently during installation.

Common Migration Pitfalls

Forgetting Service Worker Wake Events

Service workers terminate when idle. If your extension needs to respond to events after a period of inactivity, ensure all entry points are properly registered:

// Register all event listeners at top level
// Don't wrap them in functions that only get called once

// Wrong approach
function setupListeners() {
  chrome.runtime.onMessage.addListener(handleMessage);
}

// Correct approach - always at top level
chrome.runtime.onMessage.addListener(handleMessage);
chrome.runtime.onConnect.addListener(handlePort);
chrome.storage.onChanged.addListener(handleStorageChange);

Timer Persistence Issues

Avoid using setTimeout for critical operations. Chrome’s alarms API provides reliable timing that survives service worker restarts:

// Instead of setTimeout (unreliable in service workers)
setTimeout(() => doWork(), 60000);

// Use chrome.alarms (persists across restarts)
chrome.alarms.create('delayedWork', { delayInMinutes: 1 });
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === 'delayedWork') doWork();
});

Storage Access in Service Workers

Direct storage access from service workers can fail due to lifecycle issues. Always handle async operations properly:

// Service worker - proper async handling
chrome.storage.local.get(['settings']).then((result) => {
  const settings = result.settings || defaultSettings;
  processSettings(settings);
}).catch((error) => {
  console.error('Storage error:', error);
});

Testing Your Migration

Chrome provides testing capabilities before full deployment:

  1. Unpacked Extension Testing: Load your extension as an unpacked extension in chrome://extensions to debug service worker behavior
  2. Service Worker DevTools: Access the Service Worker context through the Extensions Management page
  3. Chrome Flags: Enable chrome://flags/#extension-worker-persistence during development for easier debugging

Final Considerations

Manifest V3 migration requires rethinking extension architecture around event-driven, ephemeral service workers. The changes improve security and reduce resource consumption but demand careful attention to state management, timing, and communication patterns.

The migration investment pays off through better extension performance, improved security posture, and continued access to the Chrome Web Store. Most developers complete migration within a few days, depending on extension complexity.

Built by theluckystrike — More at zovo.one