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_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_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.
| Permission | What 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, scripting | No 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 V2 | Manifest V3 |
|---|---|
| "manifest_version": 2 | "manifest_version": 3 |
| browser_action / page_action | action (one unified key; chrome.action in code) |
| background.scripts + persistent | background.service_worker (event-driven, no DOM, terminated when idle) |
| Host patterns inside permissions | Moved 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.webRequest | declarativeNetRequest 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
- Chrome extension manifest generatorBuild a valid V3 manifest interactively, simulate install warnings, and download a scaffold.
- App icon generatorOne image in, the full 16/32/48/128 PNG icon set out — every size the manifest declares.
- JSON formatterWhen Chrome says the manifest will not parse, format it and find the trailing comma in seconds.
- PWA manifest generatorSearching for the web app manifest instead? That is a different manifest.json — build it here.
Frequently asked questions
The manifest is a JSON file named manifest.json at the root of every Chrome extension. It declares the extension’s identity (name, version, icons), what it is allowed to do (permissions, host_permissions), and which code runs where (background service worker, content scripts, popup). Chrome reads it at install time — if the manifest is invalid, the extension will not load at all.
No. Chrome removed Manifest V2 support entirely in Chrome 139 — MV2 extensions no longer load even as unpacked developer extensions, and the enterprise policy escape hatches have closed. Firefox still runs MV2, but anything targeting Chrome must be Manifest V3.

