Manifest V3 Migration Guide: Update Your Chrome Extension in 2025
Google’s transition from Manifest V2 to Manifest V3 represents the most significant change to the Chrome extension platform since its inception. With Manifest V2 extensions no longer accepted for new submissions since January 2022 and existing extensions facing mandatory migration deadlines, understanding how to migrate your extension to Manifest V3 is essential for any Chrome extension developer in 2025.
This comprehensive migration guide will walk you through every aspect of moving from Manifest V2 to V3, covering the fundamental architectural changes, specific API replacements, and practical strategies for testing and deploying your migrated extension.
Why Manifest V3 Matters
Manifest V3 is not merely an incremental update — it represents a fundamental shift in how Chrome extensions operate, with a primary focus on three core principles: security, performance, and user privacy.
Enhanced Security
Manifest V3 introduces stricter requirements that reduce the attack surface of extensions. The removal of remote code execution, tighter host permissions, and mandatory declarations for all capabilities mean users can have greater confidence in what extensions can access. Every permission your extension requests must now be explicitly declared in the manifest, and users can see exactly what data your extension can access before installation.
Improved Performance
The transition from background pages to service workers brings significant performance benefits. Service workers are event-driven, lightweight, and can be terminated when not in use, reducing memory consumption dramatically. This is particularly important for users with many extensions installed, as Manifest V2 background pages remained active in memory constantly.
User Privacy
Manifest V3 gives users finer-grained control over their privacy. The new permissions system requires users to grant specific host permissions rather than broad access. Additionally, the declarativeNetRequest API replaces the webRequest API for network request modification, allowing extensions to block ads and trackers without reading user’s browsing data.
The Business Case for Migration
Beyond the technical improvements, there are compelling business reasons to migrate:
- Store Compliance: Google has set deadlines for Manifest V2 extension removal. Extensions that fail to migrate will eventually stop working.
- New Features: Many new Chrome extension APIs are Manifest V3 only, including advanced tab management, improved messaging, and enhanced storage capabilities.
- User Trust: Users are increasingly security-conscious, and extensions using modern Manifest V3 patterns signal professionalism and commitment to privacy.
Key Differences Between Manifest V2 and V3
Understanding the fundamental architectural differences is crucial for a successful migration. Here are the most significant changes you need to understand.
Background Pages Become Service Workers
The most dramatic change is the replacement of persistent background pages with ephemeral service workers. In Manifest V2, your background script ran as a persistent HTML page that stayed loaded continuously. In Manifest V3, the background script runs as a service worker that activates only when needed and terminates when idle.
This change affects how you handle state, timers, and long-running operations. You can no longer rely on global variables persisting between events, and you must use the chrome.storage API for any state that needs to persist.
webRequest to declarativeNetRequest
The powerful webRequest API that allowed extensions to analyze and modify network requests has been replaced by declarativeNetRequest. This new API works differently — instead of your extension actively intercepting and modifying each request, you define rules declaratively, and Chrome handles the matching and modification.
This change improves privacy because your extension no longer needs to read the content of network requests. Instead, you specify rules like “block all requests to this domain,” and Chrome enforces those rules without your extension ever seeing the request content.
Host Permissions Are Now Runtime
In Manifest V2, host permissions were granted at installation time. In Manifest V3, many host permissions are requested at runtime using the permissions API. This means users can grant access to specific sites when needed rather than giving your extension blanket access to all websites.
Remote Code Execution Removal
Manifest V3 prohibits loading and executing remote code. All your extension’s JavaScript must be bundled within the extension package. This is a significant security improvement but requires restructuring if your extension previously loaded scripts from external servers.
Changes to Storage API
The storage API has been enhanced in Manifest V3 with new capabilities and improved performance. The chrome.storage.session API provides session-scoped storage that doesn’t persist across browser restarts, perfect for temporary data. The chrome.storage.managed API allows administrators to configure extension settings via enterprise policies.
Step-by-Step Migration Process
Now let’s walk through the practical steps of migrating your extension from Manifest V2 to V3.
Step 1: Update Your Manifest File
The first step is updating your manifest.json to use Manifest V3. Change the manifest version and review your permissions:
{
"manifest_version": 3,
"name": "My Extension",
"version": "2.0",
"permissions": [
"storage",
"tabs"
],
"host_permissions": [
"https://example.com/*"
],
"background": {
"service_worker": "background.js"
}
}
Key changes to make:
- Change
"manifest_version"from 2 to 3 - Rename
"background": {"scripts": [...]}to"background": {"service_worker": "background.js"} - Move website URLs from
"permissions"to"host_permissions" - Review and minimize your permissions list
Step 2: Migrate Background Scripts to Service Workers
Converting your background script to a service worker requires several adjustments:
Before (Manifest V2):
// background.js in Manifest V2
chrome.runtime.onInstalled.addListener(() => {
console.log('Extension installed');
});
let count = 0;
chrome.tabs.onActivated.addListener(() => {
count++;
});
After (Manifest V3):
// background.js in Manifest V3
chrome.runtime.onInstalled.addListener(() => {
console.log('Extension installed');
});
// Use storage for state that needs to persist
chrome.tabs.onActivated.addListener(async () => {
const { count = 0 } = await chrome.storage.session.get('count');
await chrome.storage.session.set({ count: count + 1 });
});
Important service worker considerations:
- Use
chrome.storage.localorchrome.storage.sessionfor persistent state - Replace
setTimeoutandsetIntervalwithchrome.alarmsAPI - Remove all direct DOM manipulation from background scripts
- Handle the service worker lifecycle — it can terminate when idle
Step 3: Replace webRequest with declarativeNetRequest
If your extension uses the webRequest API for blocking or modifying network requests, you need to migrate to declarativeNetRequest. This requires creating rule files and updating your manifest.
In manifest.json, add:
{
"permissions": [
"declarativeNetRequest"
],
"host_permissions": [
"*://*/*"
],
"declarative_net_request": {
"rule_resources": [{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}]
}
}
Create rules.json:
[
{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": { "urlFilter": "example.com/tracker", "resourceTypes": ["script"] }
}
]
The declarativeNetRequest API is more limited than webRequest but provides better privacy guarantees and performance.
Step 4: Update Content Scripts
Content scripts largely work the same in Manifest V3, but there are some changes:
- Content scripts cannot use
eval()ornew Function()in any context - The
<script>tag with remote URLs is no longer allowed - Use
chrome.runtime.getURL()to reference extension resources
If your content script needs to communicate with the background service worker, use message passing:
// Content script
chrome.runtime.sendMessage({ action: "getData" }, (response) => {
console.log(response.data);
});
// Background service worker
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "getData") {
chrome.storage.local.get('data', (result) => {
sendResponse({ data: result.data });
});
}
return true; // Keep channel open for async response
});
Step 5: Update Action API
In Manifest V2, you used chrome.browserAction to control the extension’s toolbar icon. In Manifest V3, this has been renamed to chrome.action. Update all references:
// Manifest V2
chrome.browserAction.setBadgeText({ text: '5' });
chrome.browserAction.onClicked.addListener((tab) => { /* ... */ });
// Manifest V3
chrome.action.setBadgeText({ text: '5' });
// Note: onClicked doesn't work with action in MV3 if you have a popup
// Use the popup or listen to different events
Step 6: Review and Update Permissions
Manifest V3 has a more granular permission system. Review each permission your extension uses:
- Storage permissions: Still work the same, but consider using
storage.sessionfor ephemeral data - Tabs permission: Provides less information by default; you may need the
tabspermission for full access - Host permissions: Now separated from API permissions and requested separately
Test your extension with the minimum necessary permissions. You may find that you can reduce permissions significantly.
Storage Changes in Detail
Understanding the storage API changes is critical because state management is fundamental to most extensions.
chrome.storage.local
This API works similarly to Manifest V2 but with improved performance. Data is stored as JSON and can be synced across devices if the user is signed into Chrome with sync enabled.
chrome.storage.local.set({ key: value }).then(() => {
console.log('Data saved');
});
chrome.storage.local.get(['key']).then((result) => {
console.log(result.key);
});
chrome.storage.session
New in Manifest V3, this provides session-scoped storage that clears when the browser closes. This is perfect for temporary data that doesn’t need to persist:
chrome.storage.session.set({ temporaryData: 'value' });
// Data is available during this browser session
// Cleared when browser closes
chrome.storage.managed
This API allows enterprise administrators to push mandatory settings to extensions installed in managed environments. If your extension is used in enterprise settings, use this for configuration that users cannot change.
Testing Your Migrated Extension
Thorough testing is essential before releasing your migrated extension. Here’s how to approach it.
Local Testing
- Load your extension in Chrome using the Extensions Management page (
chrome://extensions) - Enable Developer Mode
- Click “Load unpacked” and select your extension directory
- Test all functionality:
- Extension popup interactions
- Content script behavior on various websites
- Background service worker event handling
- Storage persistence
- Network request rules (if using declarativeNetRequest)
Automated Testing
Consider using Playwright or Puppeteer to automate extension testing. These tools can:
- Load your extension in a browser instance
- Simulate user interactions
- Verify background script behavior
- Test storage operations
// Example using Puppeteer
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
args: ['--disable-extensions-except=/path/to/extension']
});
const page = await browser.newPage();
// Test your extension functionality
})();
Common Migration Bugs to Watch For
- Service worker termination: If your extension stops working after being idle, you’re likely not handling the service worker lifecycle correctly
- Missing storage initialization: Always initialize default values in storage on first run
- Timer issues: Replace setTimeout/setInterval with chrome.alarms
- Message channel closures: Remember that service workers can shut down, closing message channels
Common Migration Pitfalls and Solutions
Even experienced developers encounter challenges during Manifest V3 migration. Here are the most common issues and how to solve them.
Pitfall 1: Losing State When Service Worker Restarts
Problem: Your extension loses track of important state when the service worker terminates.
Solution: Store all important state in chrome.storage.local or chrome.storage.session. Never rely on global variables persisting.
// Bad - state will be lost
let myState = { count: 0 };
// Good - state persists across service worker restarts
async function init() {
const result = await chrome.storage.local.get('myState');
if (!result.myState) {
await chrome.storage.local.set({ myState: { count: 0 } });
}
}
Pitfall 2: Timers Not Firing
Problem: setTimeout and setInterval don’t work reliably in service workers because the worker can terminate.
Solution: Use the chrome.alarms API instead:
chrome.alarms.create('myAlarm', { delayInMinutes: 5 });
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'myAlarm') {
// Handle alarm
}
});
Pitfall 3: declarativeNetRequest Rules Not Working
Problem: Your network request blocking rules don’t seem to take effect.
Solution: Make sure:
- The rules file is properly referenced in manifest.json
- Rule IDs are unique and within the allowed range (1-2147483647)
- Rules are enabled in the
declarative_net_requestmanifest section - Check the extension service worker console for errors
Pitfall 4: Host Permissions Denied
Problem: Your extension can’t access certain websites.
Solution:
- Check that the domain is listed in
host_permissionsin manifest.json - Remember that some permissions now require user interaction to grant
- Test with the extension’s “Site Access” setting in chrome://extensions
Pitfall 5: Content Script Communication Failures
Problem: Content scripts can’t communicate with the background service worker.
Solution:
- Remember that service workers can shut down, so use proper message handling
- Use
chrome.runtime.sendNativeMessagefor communication with native applications - Ensure your content script is properly injected (check manifest.json content_scripts section)
Performance Optimization for Manifest V3
Manifest V3 service workers offer excellent performance potential, but you need to optimize your code to take advantage of it.
Minimize Service Worker Wake-ups
Every time your service worker wakes up, there’s overhead. Group your event listeners efficiently:
// Instead of multiple separate listeners
chrome.tabs.onCreated.addListener(handleTabCreated);
chrome.tabs.onUpdated.addListener(handleTabUpdated);
chrome.tabs.onRemoved.addListener(handleTabRemoved);
// Consider using a single handler for related events
chrome.tabs.onCreated.addListener(async (tab) => {
// Handle tab creation
// Update storage if needed
});
Use Storage Efficiently
Avoid excessive storage reads and writes. Batch operations when possible:
// Good - single write with multiple values
await chrome.storage.local.set({
setting1: value1,
setting2: value2,
setting3: value3
});
// Avoid - multiple separate writes
await chrome.storage.local.set({ setting1: value1 });
await chrome.storage.local.set({ setting2: value2 });
await chrome.storage.local.set({ setting3: value3 });
Lazy Load Content Scripts
Only inject content scripts when needed using the matches field in manifest.json or programmatic injection:
{
"content_scripts": [
{
"matches": ["https://example.com/*"],
"js": ["content.js"],
"run_at": "document_idle"
}
]
}
Conclusion
Migrating from Manifest V2 to V3 is a significant undertaking, but it’s essential for the future of your Chrome extension. The changes bring meaningful improvements in security, performance, and user privacy that benefit both developers and users.
The key to successful migration is understanding the fundamental architectural changes — particularly the shift from persistent background pages to ephemeral service workers. Once you understand this core difference, the individual API changes become manageable.
Remember to:
- Test thoroughly at each stage of migration
- Use Chrome’s built-in developer tools for debugging service workers
- Take advantage of new Manifest V3 features like declarativeNetRequest and session storage
- Keep your permissions list minimal to build user trust
The Chrome extension ecosystem continues to evolve, and Manifest V3 represents a solid foundation for building secure, performant, and privacy-respecting extensions. Start your migration today, and your users will thank you for the improved experience.
Related Articles
- Manifest V3 Service Worker Patterns: Anti-Patterns to Avoid - Learn common pitfalls and best practices for MV3 service workers.
- Declarative Net Request API Complete Tutorial - Master the declarativeNetRequest API for network request modification.
- Chrome Extension Web Store Publishing Checklist 2025 - Ensure your extension meets all publishing requirements.
Ready to publish your migrated extension? Check out our comprehensive guide to publishing Chrome extensions to the Web Store for step-by-step instructions.
Turn Your Extension Into a Business
Ready to monetize? The Extension Monetization Playbook covers freemium models, Stripe integration, subscription architecture, and growth strategies for Chrome extension developers.
Related Articles
- Chrome Extension Development 2025 Complete Beginners Guide
- Chrome Extension Security Best Practices
- Chrome Extension Performance Optimization Guide
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.