DEV Community

javediqbal8381
javediqbal8381

Posted on

Understanding Chrome Extensions: A Developer's Guide to Manifest V3

Understanding Chrome Extensions: A Developer's Guide to Manifest V3

Chrome extensions have become essential tools that enhance browser functionality for millions of users worldwide. As a developer, understanding how they work under the hood is crucial for building powerful, secure, and performant extensions. In this comprehensive guide, we'll dive deep into Chrome extension architecture, the Manifest V3 specification, and everything you need to know to publish your extension.

πŸ—οΈ Extension Architecture: The Core Components

Every Chrome extension is built from several key components that work together seamlessly:

1. Manifest File (manifest.json)

The manifest is the blueprint of your extension - a JSON configuration file that must reside in the root directory. It's the first thing Chrome reads and contains metadata, permissions, and component declarations.

Required fields:

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0"
}
Enter fullscreen mode Exit fullscreen mode

Common additional fields:

{
  "manifest_version": 3,
  "name": "My Awesome Extension",
  "version": "1.0.0",
  "description": "A brief description of what your extension does",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icons/icon48.png",
    "default_title": "Click me!"
  },
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["https://*/*", "http://*/*"],
      "js": ["content.js"],
      "css": ["styles.css"]
    }
  ],
  "permissions": [
    "storage",
    "tabs",
    "activeTab"
  ],
  "host_permissions": [
    "https://api.example.com/*"
  ]
}
Enter fullscreen mode Exit fullscreen mode

2. Service Worker (Background Script)

In Manifest V3, the persistent background page has been replaced with service workers. This is a major architectural change designed to improve performance and security.

Key characteristics:

  • Runs in the background only when needed (event-driven)
  • No access to the DOM
  • Automatically terminates after 30 seconds of inactivity
  • Restarts when an event is triggered

Example service worker:

// background.js
chrome.runtime.onInstalled.addListener(() => {
  console.log('Extension installed');
  chrome.storage.local.set({ initialized: true });
});

// Listen for tab updates
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete') {
    console.log('Tab loaded:', tab.url);
  }
});

// Listen for messages
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'getData') {
    // Process data
    sendResponse({ data: 'Response from background' });
  }
  return true; // Required for async sendResponse
});
Enter fullscreen mode Exit fullscreen mode

3. Content Scripts

Content scripts run in the context of web pages and can interact with the DOM. They're injected into pages matching patterns defined in the manifest.

Two ways to inject content scripts:

Declarative (via manifest):

{
  "content_scripts": [
    {
      "matches": ["https://github.com/*"],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Programmatic (via scripting API):

// In service worker
chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ['content.js']
  });
});

// Or inject a function
function injectedFunction() {
  document.body.style.backgroundColor = 'lightblue';
}

chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: injectedFunction
});
Enter fullscreen mode Exit fullscreen mode

4. Popup / Action UI

The popup is an HTML page that appears when users click your extension icon in the toolbar.

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 300px; padding: 10px; }
  </style>
</head>
<body>
  <h1>My Extension</h1>
  <button id="actionBtn">Do Something</button>
  <script src="popup.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
// popup.js
document.getElementById('actionBtn').addEventListener('click', async () => {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

  chrome.tabs.sendMessage(tab.id, {
    action: 'performAction'
  }, (response) => {
    console.log('Response:', response);
  });
});
Enter fullscreen mode Exit fullscreen mode

πŸ“‘ Communication Patterns

One of the most critical aspects of extension development is understanding how different components communicate with each other.

1. One-time Messages

From content script to service worker:

// content.js
chrome.runtime.sendMessage(
  { greeting: 'hello', data: 'some data' },
  (response) => {
    console.log('Response:', response);
  }
);
Enter fullscreen mode Exit fullscreen mode

From service worker to content script:

// background.js
chrome.tabs.sendMessage(
  tabId,
  { action: 'updateUI', data: 'new data' },
  (response) => {
    console.log('Response:', response);
  }
);
Enter fullscreen mode Exit fullscreen mode

From popup to service worker:

// popup.js
chrome.runtime.sendMessage(
  { request: 'getData' },
  (response) => {
    console.log('Data:', response.data);
  }
);
Enter fullscreen mode Exit fullscreen mode

2. Long-lived Connections

For multiple messages between components, use ports:

// content.js
const port = chrome.runtime.connect({ name: 'my-channel' });

port.postMessage({ type: 'init' });

port.onMessage.addListener((msg) => {
  console.log('Received:', msg);
});

// background.js
chrome.runtime.onConnect.addListener((port) => {
  console.log('Connected:', port.name);

  port.onMessage.addListener((msg) => {
    console.log('Message received:', msg);
    port.postMessage({ response: 'acknowledged' });
  });
});
Enter fullscreen mode Exit fullscreen mode

3. Communication with Web Pages

Content scripts and web pages share the DOM but have isolated JavaScript contexts. Use window.postMessage:

// content.js
window.addEventListener('message', (event) => {
  if (event.source !== window) return;

  if (event.data.type === 'FROM_PAGE') {
    console.log('Message from page:', event.data.text);
    // Forward to service worker if needed
    chrome.runtime.sendMessage(event.data);
  }
});

// webpage.js
window.postMessage(
  { type: 'FROM_PAGE', text: 'Hello from webpage!' },
  '*'
);
Enter fullscreen mode Exit fullscreen mode

πŸ” Permissions System

Permissions in Manifest V3 are more granular and security-focused. There are four types:

1. Regular Permissions

API permissions that don't require host access:

{
  "permissions": [
    "storage",        // chrome.storage API
    "tabs",          // Limited tabs API access
    "activeTab",     // Access to active tab (requires user gesture)
    "contextMenus",  // Create context menu items
    "notifications", // Show notifications
    "scripting"      // Execute scripts programmatically
  ]
}
Enter fullscreen mode Exit fullscreen mode

2. Host Permissions

Access to specific URLs or patterns:

{
  "host_permissions": [
    "https://api.example.com/*",
    "https://*.google.com/*",
    "<all_urls>"  // Access to all URLs (use sparingly!)
  ]
}
Enter fullscreen mode Exit fullscreen mode

3. Optional Permissions

Requested at runtime rather than install time:

{
  "optional_permissions": ["downloads", "bookmarks"],
  "optional_host_permissions": ["https://*/*"]
}
Enter fullscreen mode Exit fullscreen mode

Request them in your code:

document.getElementById('enableFeature').addEventListener('click', async () => {
  const granted = await chrome.permissions.request({
    permissions: ['downloads'],
    origins: ['https://example.com/*']
  });

  if (granted) {
    console.log('Permission granted!');
  }
});
Enter fullscreen mode Exit fullscreen mode

4. Content Script Matches

Separate from host_permissions - controls where content scripts inject:

{
  "content_scripts": [
    {
      "matches": ["https://github.com/*"],
      "js": ["content.js"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

⚑ Manifest V3 Key Changes

If you're migrating from V2 or starting fresh, here are the critical changes:

Background Pages β†’ Service Workers

// ❌ Manifest V2
{
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  }
}

// βœ… Manifest V3
{
  "background": {
    "service_worker": "background.js",
    "type": "module"  // Optional: enables ES modules
  }
}
Enter fullscreen mode Exit fullscreen mode

Host Permissions Separation

// ❌ Manifest V2
{
  "permissions": [
    "tabs",
    "https://*/*"
  ]
}

// βœ… Manifest V3
{
  "permissions": ["tabs"],
  "host_permissions": ["https://*/*"]
}
Enter fullscreen mode Exit fullscreen mode

executeScript Migration

// ❌ Manifest V2
chrome.tabs.executeScript(tabId, {
  code: 'document.body.style.backgroundColor = "red"'
});

// βœ… Manifest V3
chrome.scripting.executeScript({
  target: { tabId: tabId },
  func: () => {
    document.body.style.backgroundColor = "red";
  }
});
Enter fullscreen mode Exit fullscreen mode

webRequest β†’ declarativeNetRequest

For content blocking and request modification:

{
  "permissions": ["declarativeNetRequest"],
  "declarative_net_request": {
    "rule_resources": [{
      "id": "ruleset_1",
      "enabled": true,
      "path": "rules.json"
    }]
  }
}
Enter fullscreen mode Exit fullscreen mode

No Remote Code

All code must be bundled with the extension. External scripts from CDNs are no longer allowed for security reasons.

πŸ“¦ What You Can Do Without Permissions

Some Chrome APIs don't require permissions:

  • chrome.runtime (messaging, extension info)
  • chrome.i18n (internationalization)
  • chrome.action (basic toolbar icon interaction)
  • window and document APIs in content scripts
  • Standard web APIs (fetch, localStorage in content scripts)

πŸš€ Publishing to Chrome Web Store

Step 1: Prepare Your Extension

  1. Test thoroughly - Load unpacked in chrome://extensions/
  2. Create assets:

    • 128x128 icon (required)
    • 440x280 or 920x680 screenshots (required)
    • 1280x800 promotional image (optional)
    • Privacy policy (if handling user data)
  3. Package your extension:

   # Create a zip file with manifest.json at root
   zip -r extension.zip . -x "*.git*" "node_modules/*" ".DS_Store"
Enter fullscreen mode Exit fullscreen mode

Step 2: Register as Developer

  1. Go to Chrome Web Store Developer Dashboard
  2. Pay $5 one-time registration fee
  3. Enable 2-factor authentication (required as of 2024)

Step 3: Upload Extension

  1. Click "New Item"
  2. Upload your zip file
  3. Fill out the Store Listing:

    • Description - Clear explanation of functionality
    • Category - Choose appropriate category
    • Language - Target language(s)
    • Screenshots - At least 1, up to 5
  4. Privacy Tab:

    • Single Purpose - Declare extension's purpose
    • Permission Justifications - Explain why each permission is needed
    • Data Usage - Disclose any data collection
  5. Distribution Tab:

    • Visibility - Public, Unlisted, or Private
    • Regions - Select target countries
    • Pricing - Free or paid

Step 4: Submit for Review

Review times vary but most extensions are reviewed within 3 days. You'll receive an email notification once reviewed.

Publishing Options:

Public: Available to everyone in Chrome Web Store
Unlisted: Only accessible via direct link
Private: Only for users in your domain/organization

Step 5: Update Your Extension

To publish updates:

  1. Increment version number in manifest.json
  2. Upload new zip file
  3. Submit for review
  4. Can use percentage rollout to gradually release to users

Programmatic Publishing (CI/CD)

You can automate publishing using the Chrome Web Store API:

// Using chrome-webstore-upload npm package
import chromeWebstoreUpload from 'chrome-webstore-upload';

const store = chromeWebstoreUpload({
  extensionId: 'your-extension-id',
  clientId: process.env.CLIENT_ID,
  clientSecret: process.env.CLIENT_SECRET,
  refreshToken: process.env.REFRESH_TOKEN
});

const myZipFile = fs.createReadStream('./extension.zip');
await store.uploadExisting(myZipFile);
await store.publish();
Enter fullscreen mode Exit fullscreen mode

πŸ› οΈ Development Tips & Best Practices

  1. Use ES Modules - Set "type": "module" in background config
  2. Handle Service Worker Lifecycle - Store state in chrome.storage, not variables
  3. Minimize Permissions - Only request what you absolutely need
  4. Test on Multiple Chrome Versions - Use beta/dev channels
  5. Monitor Performance - Service workers should be lightweight
  6. Implement Error Handling - Always check chrome.runtime.lastError
  7. Use Promises - Modern Chrome APIs support promises in the chrome.* namespace
// Modern promise-based approach
const tabs = await chrome.tabs.query({ active: true });
const result = await chrome.storage.local.get('key');
Enter fullscreen mode Exit fullscreen mode

🎯 Common Pitfalls to Avoid

  1. ❌ Using localStorage in service workers (not available)
  2. ❌ Assuming service worker stays alive indefinitely
  3. ❌ Not returning true from onMessage listener for async responses
  4. ❌ Requesting <all_urls> without justification
  5. ❌ Including unnecessary files in your package
  6. ❌ Not handling permission denials gracefully
  7. ❌ Using eval() or inline scripts (CSP violation)

🎬 Conclusion

Chrome extensions are powerful tools that can significantly enhance user experience. With Manifest V3, Google has made extensions more secure, performant, and privacy-focused. While the migration from V2 requires some changes, the improved architecture leads to better extensions overall.

πŸ“š Resources


About Me: I've developed multiple complex Chrome extensions over the years, ranging from productivity tools to advanced web automation solutions. Through this journey, I've learned the intricacies of extension architecture, performance optimization, and the evolving Chrome extension ecosystem. If you have questions about Chrome extension development, feel free to reach out!

My site >> https://justinbrowser.com/

Happy coding! πŸš€

Top comments (0)