consolelog.tools
Manifest V3 reference

The Chrome extension manifest, explained field by field

Every Chrome extension starts with manifest.json — one file at the extension root that names it, declares what it may do, and wires up every piece of code it runs. Get a key wrong and Chrome refuses to load the extension entirely, usually with an error message that names the key but not the fix.

This page is the fix-first reference: the complete field list for Manifest V3 (the only version Chrome still loads — V2 was removed in Chrome 139), the install warnings each permission triggers, the MV2 → MV3 migration map, and the most common load errors. When you would rather not hand-write it, the manifest generator builds a valid V3 manifest interactively and simulates the install-warning prompt for your permission set.

A minimal manifest.json — and a realistic one

Three keys are genuinely required: manifest_version, name, and version. This is the smallest manifest Chrome will load, plus the two recommended keys the Web Store expects:

manifest.json — minimal, loads in Chrome
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "What the extension does, in one sentence.",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

A real extension adds a toolbar popup, a background service worker, a content script, and split permissions — this shape covers the majority of extensions shipped today:

manifest.json — popup + service worker + content script
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "description": "Highlights prices on shopping pages.",
  "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" },

  "action": {
    "default_popup": "popup.html",
    "default_title": "My Extension"
  },

  "background": {
    "service_worker": "background.js"
  },

  "content_scripts": [
    {
      "matches": ["https://*.example.com/*"],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ],

  "permissions": ["storage", "activeTab"],
  "host_permissions": ["https://*.example.com/*"]
}

Every Manifest V3 field that matters

Ordered roughly by how often you will touch them. “Required” means Chrome refuses to load the extension without it.

manifest_versionRequired
Must be the number 3. Chrome removed Manifest V2 support entirely in Chrome 139, so 2 no longer loads — not as an unpacked extension, not from the Web Store.
nameRequired
The extension name shown in chrome://extensions, the toolbar, and the Web Store. Maximum 75 characters; keep it short since menus truncate it.
versionRequired
One to four dot-separated integers, each 0–65535 (e.g. "1.4.2"). No suffixes like "-beta" — Chrome rejects them. The Web Store requires every upload to increase this value.
descriptionRecommended
Plain-text summary, max 132 characters, shown in chrome://extensions and as the Web Store fallback description. No HTML.
iconsRecommended
PNG sizes keyed by pixel dimension. 128 is used by the Web Store, 48 by the extensions page, 16 as favicon. Supply at least 16, 48, and 128 — Chrome scales the rest.
actionOptional
The toolbar button: default_popup (an HTML file), default_icon, default_title. Replaces both browser_action and page_action from MV2 — those keys are invalid in V3.
backgroundOptional
In V3 this is { "service_worker": "background.js" } — an event-driven worker Chrome terminates when idle. persistent and scripts are MV2-only and rejected. Add "type": "module" to use import statements.
content_scriptsOptional
JS/CSS injected into pages that match the matches patterns. run_at controls timing (document_idle default). world: "MAIN" runs in the page’s own JS context instead of the isolated one.
permissionsOptional
Chrome API capabilities only — storage, tabs, scripting, alarms, notifications. In V3 host patterns do NOT belong here; they were split into host_permissions.
host_permissionsOptional
URL match patterns the extension can read and modify (fetch across origins, inject via scripting API). Broad patterns like <all_urls> trigger the scariest install warning and slower Web Store review.
optional_permissionsOptional
Permissions requested at runtime with chrome.permissions.request(), after a user gesture — the polite pattern that keeps the install prompt clean. optional_host_permissions is the host equivalent.
web_accessible_resourcesOptional
Extension files that web pages may load. V3 requires an array of { resources, matches } objects — the MV2 flat array of strings is a load error.
content_security_policyOptional
V3 takes an object: { "extension_pages": "...", "sandbox": "..." }. extension_pages cannot allow remote code — no remote script-src at all. The MV2 single-string form is rejected.
options_page / options_uiOptional
The settings surface. options_ui with "open_in_tab": false renders it embedded in chrome://extensions; options_page opens a full tab.
commandsOptional
Keyboard shortcuts. The reserved _execute_action command triggers the toolbar action. Users can rebind everything at chrome://extensions/shortcuts.
side_panelOptional
Points at the HTML for Chrome’s side panel (Chrome 114+). Requires the sidePanel permission.
declarative_net_requestOptional
Static rulesets for network request blocking/redirecting — the V3 replacement for blocking webRequest. Rule files are declared here, capabilities via the declarativeNetRequest permission.
externally_connectableOptional
Which websites and other extensions may message yours via chrome.runtime.sendMessage. Unset means every extension can, but no website can.
default_localeOptional
Required if and only if a _locales directory exists. Names the fallback language for __MSG_key__ placeholders used anywhere in the manifest.
minimum_chrome_versionOptional
Blocks installation on older Chrome versions. Set it when you rely on newer APIs like side_panel or offscreen documents.

Permissions and the warnings users actually see

Install warnings are the single biggest driver of install abandonment and Web Store review time, and they are determined mechanically by your permission list. The most common surprise: tabs sounds harmless but reads as a browsing-history grab.

PermissionWhat the user is told
tabs“Read your browsing history” — the URL/title of every tab counts as history. activeTab usually avoids this.
<all_urls> or *://*/* host pattern“Read and change all your data on all websites” — the heaviest warning and the biggest review-time cost.
history“Read and change your browsing history on all your signed-in devices”.
bookmarks“Read and change your bookmarks”.
downloads“Manage your downloads”.
storage, alarms, activeTab, scriptingNo warning at all — these are the ones to prefer when a quieter install prompt matters.

The manifest generator renders this prompt live for your exact permission set, so you can trade tabs for activeTab and watch the warning disappear before you ship.

Manifest V2 → V3: the migration map

Most of the migration is mechanical renames. The two changes that cost real engineering time are the persistent background page becoming a terminate-anytime service worker, and blocking webRequest becoming declarative rules.

Manifest V2Manifest V3
"manifest_version": 2"manifest_version": 3
browser_action / page_actionaction (one unified key; chrome.action in code)
background.scripts + persistentbackground.service_worker (event-driven, no DOM, terminated when idle)
Host patterns inside permissionsMoved to host_permissions
web_accessible_resources: ["img.png"]Array of { "resources": [...], "matches": [...] } objects
content_security_policy: "script-src …" (string)Object form { "extension_pages": "…" }; remote code is banned outright
Blocking chrome.webRequestdeclarativeNetRequest static/dynamic rules
chrome.tabs.executeScript()chrome.scripting.executeScript() (needs the scripting permission)

The full story — timeline, service-worker patterns, and what broke for real extensions — is in the Manifest V3 field guide.

Common manifest errors and their fixes

“Manifest version 2 is deprecated / unsupported”

Chrome 139+ refuses MV2 outright. Migrate with the table above — the mechanical renames take minutes; budget real time only for background-page → service-worker and webRequest → declarativeNetRequest changes.

“Service worker registration failed. Status code: 3”

The service_worker path is resolved from the extension root and the file must exist there. If background.js uses import, also add "type": "module" to the background object.

“Invalid value for web_accessible_resources[0]”

You used the MV2 flat string array. V3 requires objects: [{ "resources": ["img.png"], "matches": ["https://example.com/*"] }].

“Invalid value for content_security_policy”

V3 rejects the MV2 string. Use { "extension_pages": "script-src 'self'; object-src 'self'" } — and note no remote hosts are allowed in script-src.

“Permission is unknown or URL pattern is malformed”

Usually a host pattern still sitting in permissions. Move URL patterns to host_permissions; keep only API names like storage or tabs in permissions.

Could not load icon — file missing or wrong path

Icon paths are relative to the manifest. Every size you declare must exist as a real PNG at that path — generate the full 16/32/48/128 set at once with the app icon generator below.

Build the whole extension package here

Frequently asked questions