Skip to main content
Technology & EngineeringBrowser Extension216 lines

Manifest V3

Chrome Manifest V3 fundamentals including configuration, permissions, and migration from V2

Quick Summary13 lines
You are an expert in Chrome Manifest V3 for building browser extensions.

## Key Points

- **`permissions`** — API access granted at install time (e.g., `storage`, `alarms`, `tabs`).
- **`host_permissions`** — URL patterns the extension can interact with. Shown to the user at install.
- **`optional_permissions`** and **`optional_host_permissions`** — Requested at runtime via `chrome.permissions.request()`.
- Request the minimum permissions needed; prefer `optional_permissions` for features that not all users need.
- Use `activeTab` instead of broad host permissions when the extension only acts on the current tab in response to a user gesture.
- Provide multiple icon sizes (16, 32, 48, 128) for crisp rendering at different display densities.
- Forgetting that `host_permissions` are now separate from `permissions` — placing URL patterns inside `permissions` will cause a manifest error.
skilldb get browser-extension-skills/Manifest V3Full skill: 216 lines
Paste into your CLAUDE.md or agent config

Manifest V3 — Browser Extension Development

You are an expert in Chrome Manifest V3 for building browser extensions.

Overview

Manifest V3 (MV3) is the current extension platform for Chrome and Chromium-based browsers. It replaces Manifest V2 with a focus on security, privacy, and performance by moving to service workers, declarative APIs, and tighter permission models.

Core Concepts

Manifest Structure

Every extension requires a manifest.json at its root:

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "A brief description of the extension.",
  "permissions": ["storage", "activeTab"],
  "host_permissions": ["https://*.example.com/*"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["https://*.example.com/*"],
      "js": ["content.js"],
      "css": ["content.css"]
    }
  ],
  "options_page": "options.html",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

Permissions Model

MV3 separates permissions into three categories:

  • permissions — API access granted at install time (e.g., storage, alarms, tabs).
  • host_permissions — URL patterns the extension can interact with. Shown to the user at install.
  • optional_permissions and optional_host_permissions — Requested at runtime via chrome.permissions.request().
// Request optional permission at runtime
document.getElementById('enable-btn').addEventListener('click', async () => {
  const granted = await chrome.permissions.request({
    permissions: ['bookmarks'],
    origins: ['https://api.example.com/*']
  });
  if (granted) {
    console.log('Permission granted');
  }
});

Action API

MV3 unifies browser_action and page_action into a single action API:

// Set badge text
chrome.action.setBadgeText({ text: '5' });
chrome.action.setBadgeBackgroundColor({ color: '#4688F1' });

// Handle clicks when no popup is defined
chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ['inject.js']
  });
});

Scripting API

Replaces chrome.tabs.executeScript with a more capable chrome.scripting API:

// Inject a script into a tab
await chrome.scripting.executeScript({
  target: { tabId: tab.id },
  func: (greeting) => {
    document.title = greeting;
  },
  args: ['Hello from extension']
});

// Insert CSS
await chrome.scripting.insertCSS({
  target: { tabId: tab.id },
  css: 'body { border: 2px solid red; }'
});

Implementation Patterns

Declarative Net Request (DNR)

MV3 replaces the blocking webRequest API with declarative rules:

// manifest.json
{
  "permissions": ["declarativeNetRequest"],
  "declarative_net_request": {
    "rule_resources": [
      {
        "id": "ruleset_1",
        "enabled": true,
        "path": "rules.json"
      }
    ]
  }
}
// rules.json
[
  {
    "id": 1,
    "priority": 1,
    "action": { "type": "block" },
    "condition": {
      "urlFilter": "tracker.example.com",
      "resourceTypes": ["script", "image"]
    }
  },
  {
    "id": 2,
    "priority": 1,
    "action": {
      "type": "redirect",
      "redirect": { "url": "https://safe.example.com/page" }
    },
    "condition": {
      "urlFilter": "unsafe.example.com/page",
      "resourceTypes": ["main_frame"]
    }
  }
]

Using ES Modules in Service Workers

{
  "background": {
    "service_worker": "background.js",
    "type": "module"
  }
}
// background.js
import { handleInstall } from './handlers/install.js';
import { handleMessage } from './handlers/messages.js';

chrome.runtime.onInstalled.addListener(handleInstall);
chrome.runtime.onMessage.addListener(handleMessage);

Best Practices

  • Request the minimum permissions needed; prefer optional_permissions for features that not all users need.
  • Use activeTab instead of broad host permissions when the extension only acts on the current tab in response to a user gesture.
  • Provide multiple icon sizes (16, 32, 48, 128) for crisp rendering at different display densities.

Core Philosophy

Manifest V3 is designed around the principle of least privilege. Every capability your extension needs — from API access to host interaction — must be explicitly declared. This is not bureaucracy; it is a security contract with the user. Design your extension to request the absolute minimum permissions needed, and prefer runtime permission requests for features that not all users will use.

Declarative APIs are the future of extension development. MV3 pushes developers away from imperative, observation-based patterns (like blocking webRequest) toward declarative rules (like declarativeNetRequest). While this feels restrictive at first, declarative APIs are more performant, more secure, and easier for the browser to optimize. Embrace the declarative model rather than searching for workarounds to the imperative one.

The manifest is not just configuration — it is documentation. A well-structured manifest tells reviewers, users, and your future self exactly what the extension does and what it needs. Every permission should have a clear justification, every content script should have precise match patterns, and every icon size should be provided. Treat the manifest with the same care you give to your code.

Anti-Patterns

  • Requesting <all_urls> as a host permission — granting access to every website when the extension only interacts with a few domains exposes users to unnecessary risk and triggers reviewer scrutiny; use specific match patterns instead.

  • Ignoring the declarativeNetRequest migration — continuing to rely on webRequest blocking mode (which is enterprise-only in MV3) instead of learning and adopting declarative rules blocks your extension from publishing on the Chrome Web Store.

  • Omitting optional permissions — bundling every permission as required at install time instead of using optional_permissions for non-essential features increases the install-time permission prompt and reduces install rates.

  • Using overly broad content script matches — matching *://*/* to inject content scripts on every page wastes resources and increases the attack surface; scope content scripts to the specific domains they need.

  • Neglecting the CSP implications of MV3 — MV3 enforces stricter Content Security Policies that disallow eval(), inline scripts, and remote code execution in extension pages; code that relies on these patterns must be refactored.

Common Pitfalls

  • Forgetting that host_permissions are now separate from permissions — placing URL patterns inside permissions will cause a manifest error.
  • Assuming webRequest blocking mode is still available — MV3 requires declarativeNetRequest for request modification; only webRequest in observe mode is permitted without the webRequestBlocking permission (which is enterprise-only).

Install this skill directly: skilldb add browser-extension-skills

Get CLI access →