manifest.json In Depth: MV3 Field Quick Reference
💡 One-sentence summary: manifest.json is the one file an extension cannot do without—3 platform-required fields as the base, 2 store-required fields for publishing, and the rest added as needed for functionality.
In the previous lesson C2 Your First Chrome Extension, the manifest.json was copied verbatim: it runs, but you may not be able to explain why each field is written the way it is. This lesson takes it apart field by field—which fields cause Chrome to refuse to load outright, which are only checked at publishing time, and how to write the high-frequency optional fields. From now on, whether you write it yourself or have AI generate it, you will be able to read it, modify it, and debug it.
What you'll be able to do after this lesson
- Name the 3 platform-required fields and 2 store-required fields of manifest.json, along with their length and format restrictions
- Starting from the minimal example, add action, background, content_scripts, and permissions one by one into your own manifest
- Use an MV3 field quick-reference table to look up the purpose of all 46 fields
- Avoid the three big beginner pitfalls: writing comments in JSON, copying old MV2 tutorials, and non-compliant version numbers
- Go through a checklist before publishing, and know which pieces of information cannot be changed after upload
Your current struggles
- "AI generated a manifest.json for me, but I can't understand at all which fields are required or what each field does"
- "I copied from old tutorials online and the fields never matched—only later did I learn that was MV2 syntax, so it was all wasted effort"
- "It loaded fine locally, but as soon as I uploaded it to the store I got Cannot parse the manifest, and I searched forever without finding the error"
When to use this technique
When you need to:
- Look up the syntax and restrictions of a specific manifest field (how long can name be, how many segments can version have)
- Build a loadable manifest.json from scratch, or add fields for new features to an existing extension
- Check the required items before publishing, nipping "finding the mistake only after upload" in the bud
And you don't want to:
- Read through the official documentation's full explanations of 46 fields—the quick-reference table is one screen below
- Have Chrome refuse to load or the store reject your extension because of a miswritten field
- Confuse MV2 and MV3 syntax, copy the wrong tutorial, and think Chrome is broken
🎒 Preparation before starting
- Have completed C2 Your First Chrome Extension, with that runnable Hello World manifest.json on hand
- A tool that can edit JSON files (Notepad works, editors like VS Code are more comfortable)
- The
chrome://extensionsextension management page open, so you can refresh and verify at any time - 10 minutes
Core idea
Chrome has only one hard requirement for each extension: there must be a manifest.json in the root directory, which lists the extension's structure and behavior information—Chrome relies on it to recognize the extension, and the store relies on it to review the extension.
The official documentation divides all 46 fields into four groups; beginners should memorize the first two groups first:
| Group | Fields | What happens if missing |
|---|---|---|
| Platform-required (3) | manifest_version, name, version | Chrome refuses to load outright and shows an error dialog |
| Required for store publishing (2) | description, icons | Runs locally, but fails store review |
| Optional (37) | action, background, content_scripts, permissions, etc. | Not missing anything; add as needed for functionality |
| Optional · ChromeOS-only (4) | file_handlers, etc. | Basically never used by desktop Chrome extensions |
Two iron rules that run through this whole page—note them down first:
- You cannot write comments in JSON—uploading to the store will report
Cannot parse the manifest - manifest.json must be in the root directory——when loading locally you select the extension folder, and when packaging a zip for the store, manifest must also be at the root of the archive, not wrapped in an extra folder
Follow along
Next, we will grow a manifest.json from 3 fields all the way to 10. Edit inside the Hello World folder from the previous lesson, and after each step go back to chrome://extensions and click the refresh icon on the extension card — after changing the manifest you must refresh the extension for it to take effect.
Step 1: Write down the three platform-required fields——the foundation of manifest.json
Why: The first thing Chrome does when loading an extension is read manifest.json; if any of these three fields is missing, loading fails outright.
Create (or open) manifest.json in the extension root directory and write:
{
"manifest_version": 3,
"name": "My First Extension",
"version": "1.0.0"
}The constraints for each of the three fields:
| Field | Constraint | Notes |
|---|---|---|
manifest_version | Only 3 is supported | MV3 is the only accepted form today; don't copy the 2 from old tutorials |
name | At most 75 characters | Displayed in the store, the install dialog, and the chrome://extensions page |
version | 1-4 dot-separated integers, each 0-65536 | e.g. 1.0.0; if the format is wrong, Chrome reports a clear error |
You should see: a JSON with only three keys——that's all the platform requires.
To see the format rules in action, deliberately change version to "1.0.0.0.0" (five segments) and refresh the extension; Chrome will pop up an error dialog: Required value version is missing or invalid. It must be between 1-4 dot-separated integers each between 0 and 65536.——change it back to 1.0.0 to recover.
- Quick check: manifest.json is in the root directory, all three fields are present, and the JSON contains no comments
Step 2: Add description and icons——completing the official minimal example
Why: These two items don't affect local running, but the Chrome Web Store checks them when you publish; the official documentation's "minimal manifest example" also includes them.
Add these two items to the JSON you just wrote, and you have the official documentation's Minimal Manifest verbatim (you may replace name with your own extension's name):
{
"manifest_version": 3,
"name": "Minimal Manifest",
"version": "1.0.0",
"description": "A basic example extension with only required keys",
"icons": {
"48": "images/icon-48.png",
"128": "images/icon-128.png"
}
}description: at most 132 characters, displayed in the store and the extensions management pageicons: one or more icons, with sizes as keys and image paths as values
You should see: at this point it is exactly the official Minimal Manifest. The image icons points to must actually exist——the icon file used in the previous lesson's Hello World works; just change the path in icons to its actual location.
- Quick check: description is no more than 132 characters; every path in icons has a corresponding file in the folder
Step 3: Add action and background——the popup and the event hub
Why: action determines what happens when the toolbar icon is clicked; background specifies the extension's service worker (event handler). The popup in the previous lesson was the work of action.
Add these two keys to the manifest (note the comma added after the previous key):
"action": {
"default_popup": "hello.html",
"default_icon": "hello_extensions.png"
},
"background": {
"service_worker": "service-worker.js"
}action: defines the toolbar icon's appearance and behavior;default_popupspecifies the page that pops up when the icon is clicked, anddefault_iconspecifies the icon imagebackground: the JS file thatservice_workerpoints to is the extension's "brain"——started on demand, stopped when idle; we'll cover the deeper techniques in the next lesson
You should see: after refreshing the extension, clicking the toolbar icon opens the popup as usual. Remember to create the service-worker.js file first (a single line of console.log("background ready") is fine for now)——every file path declared in the manifest must actually exist.
Step 4: Add the content_scripts configuration——scripts run automatically on specified pages
Why: If you want the extension to "automatically modify a page as soon as a certain kind of page opens", the static content_scripts declaration is what does it——no button clicks needed, the script injects itself.
Add this section to the manifest:
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
]matches: required. Determines which pages the script is injected into, written in match pattern syntax (e.g.https://*.nytimes.com/*); every automatically running content script must specify a match patternjs/css: optional. JS / CSS files injected in array order, with paths relative to the extension root directory
You should see: after refreshing the extension, open a page matching matches and the script takes effect automatically. After changing a content script file, refresh the extension and also refresh the host page. As for "why can't my script and the page's variables see each other"——the next lesson C4 covers isolated worlds.
Step 5: Add the permissions and host_permissions declarations
Why: permissions enables specific extension APIs; without declaring them they cannot be called; host_permissions declares which websites the extension can interact with——permissions for these sites are requested from the user at install time.
Add these two lines to the manifest:
"permissions": ["storage"],
"host_permissions": ["https://*.example.com/"]permissions: an array of the API names to enable, e.g.storage,scripting,activeTab; declare only what you actually usehost_permissions: lists the pages you can interact with using match patterns
You should see: refresh the extension and it loads normally. A misspelled permission name won't be rejected at load, but a red Errors button appears on the extensions page——for example, writing activeTab as activetab yields the message Permission 'activetab' is unknown or URL pattern is malformed.. The full debugging playbook is in C5.
Step 6: Verify with a local load, then check item by item before publishing
Why: Only Chrome and the store have the final say on whether the fields are written correctly. Run it locally first, then check again before publishing, and you avoid the most expensive rework.
Steps:
- Go back to
chrome://extensionsand click the refresh icon on the extension card (the three-step loading method was covered in the previous lesson) - If loading fails, an error dialog pops up——fix the manifest according to the error, then refresh again
- When preparing to publish, check item by item:
| Check item | Standard |
|---|---|
name / description | ≤75 / ≤132 characters, no typos—manifest metadata cannot be changed after upload; if you find a typo, the only fix is to modify the file, bump the version number, and repackage |
version | must be greater than the previously uploaded version; for the first release, set a low value such as 0.0.0.1 to leave room for future increments |
| Comments | The JSON must not contain any comments, otherwise the upload fails with Cannot parse the manifest |
| Packaging | The root of the zip archive must contain manifest.json directly, with no extra folder wrapping it |
You should see: local loading with no error dialog and no red Errors; the published zip shows manifest.json at the first level when unpacked. The full workflow of registration, submission, and review is in C6.
📋 MV3 Field Quick Reference
💡 How to use this table
Ask two questions first: which layer does this field belong to (platform-required / store-required / optional)? Which group does the feature I need fall into (UI / permissions / network / distribution)? Locate the row, write it accordingly, check the constraints, then go back to the corresponding step on the page for expanded details. The fields and constraints in this table are compiled from Chrome's official Manifest reference documentation.
Platform-required (3 fields)
| Field | Constraints | Purpose |
|---|---|---|
manifest_version | Integer, the only supported value is 3 | Manifest format version |
name | ≤75 characters | Name shown in the store, the install dialog, and chrome://extensions |
version | 1-4 dot-separated integers, each segment 0-65536 | Version number; a newly uploaded version must be greater than the previous one |
Required for store listing (2 fields)
| Field | Constraints | Purpose |
|---|---|---|
description | ≤132 characters | Description for the store and the extensions management page |
icons | One or more icons | Icon representing the extension |
Frequently used optional fields (get to know these 5 first)
| Field | Key points for writing | Purpose |
|---|---|---|
action | default_popup points to the popup page, default_icon specifies the icon | Appearance and behavior of the toolbar icon |
background | {"service_worker": "service-worker.js"} | specifies the service worker that acts as the event handler |
content_scripts | matches required + js / css arrays | JS/CSS automatically injected when a matching page is opened |
permissions | Array of API names, e.g. ["storage"] | Enables specific extension APIs |
host_permissions | Array of match patterns | Declares the web pages the extension can interact with; authorization is requested at install time |
Other optional fields (grouped by purpose)
UI and interaction
| Field | Purpose |
|---|---|
commands | Defines keyboard shortcuts within the extension |
options_page | Specifies options.html as the extension's settings page |
options_ui | Lets users modify extension options within the extensions management page |
side_panel | Specifies the HTML page displayed in the side panel |
omnibox | Registers a keyword in the Chrome address bar |
chrome_url_overrides | Overrides Chrome's default pages |
devtools_page | Defines pages that use the DevTools API |
Permissions and security
| Field | Purpose |
|---|---|
optional_permissions | Declares optional API permissions |
optional_host_permissions | Declares optional host permissions |
content_security_policy | Restricts the scripts, styles, and other resources the extension can load |
externally_connectable | Specifies which web pages and extensions can connect to your extension |
web_accessible_resources | Defines files inside the extension that web pages or other extensions can access |
sandbox | Defines sandboxed pages that cannot use extension APIs directly |
incognito | Behavior in Incognito mode, values spanning / split / not_allowed |
key | Specifies the extension ID for development purposes |
Network and data
| Field | Purpose |
|---|---|
declarative_net_request | Static rules for declarative network requests, capable of intercepting and modifying network requests |
oauth2 | Configure OAuth 2.0 (client_id and scopes) |
storage | Declare a JSON schema for a managed storage area |
tts_engine | Register the extension as a text-to-speech engine |
mime_types_handler | Register the extension as a handler for a MIME type (e.g., application/pdf) |
requirements | List the technologies required to use the extension |
Version and distribution
| Field | Purpose |
|---|---|
version_name | A human-readable version alias (e.g., "1.0 beta"); if omitted, version is shown |
minimum_chrome_version | The minimum Chrome version required to install the extension; lower versions show it as incompatible, cannot install it, and it does not auto-update |
homepage_url | The extension's homepage URL; defaults to the store page if omitted |
update_url | The update page URL for self-hosted distribution (not listed on the store) |
import | Allow importing resources into the extension |
export | Allow exporting resources from the extension |
Internationalization and display
| Field | Purpose |
|---|---|
default_locale | The default language of a multilingual extension (e.g., en); required for localized extensions, and cannot be used by non-localized extensions |
short_name | A short name, ≤12 characters; if omitted, name is truncated for display |
Chrome settings
| Field | Purpose |
|---|---|
chrome_settings_overrides | Override some Chrome settings |
cross_origin_embedder_policy | Configure the Cross-Origin-Embedder-Policy request header |
cross_origin_opener_policy | Configure the Cross-Origin-Opener-Policy request header |
ChromeOS-specific (4 in total)
| Field | Purpose |
|---|---|
file_browser_handlers | Access ChromeOS file-browser-related APIs |
file_handlers | Specify the file types handled by a ChromeOS extension |
file_system_provider_capabilities | Let the extension create a file system usable on ChromeOS |
input_components | Use the Input Method Editor (IME) APIs |
Checkpoint ✅
- manifest.json is in the extension root directory, and there are no comments anywhere in the JSON
manifest_versionis 3;nameis ≤75 characters;versionis 1-4 dot-separated integers- If publishing to the store:
descriptionis ≤132 characters, and every path iniconsactually exists - The
matchesofcontent_scriptsis filled in and follows the match pattern syntax permissionsonly declares APIs that are actually used, with no typos
Pitfall warnings
❌ Writing comments in manifest.json
{
"manifest_version": 3, // MV3 写死
"name": "My Extension"
}Reason: JSON does not support comments, and the store throws Cannot parse the manifest at upload time. Correct approach: Remove all comments before uploading; if you want to keep notes, put them in the README or a nearby document.
❌ Copying old tutorials and writing manifest_version as 2
Reason: Many tutorials online still use MV2-era syntax, while in MV3 the only supported value for manifest_version is 3, so copying the old syntax is wasted effort. Correct approach: Always use "manifest_version": 3 for new extensions; double-check any fields you are unsure about against the cheat sheet on this page.
❌ Writing version carelessly and having the new upload rejected
"version": "1.0.0.0.0" // ❌ 五段,超了
"version": "0.0.0.1" // ✅ 初版设低值,以后只往上加Reason: version must be 1-4 dot-separated integers, each segment 0-65536; moreover, the store requires each newly uploaded version number to be greater than the previous one. Correct approach: Set a low value like 0.0.0.1 for the initial release and only increment with each release, so you always have room.
❌ Discovering a typo in name only after uploading
Reason: After uploading to the store, the manifest metadata cannot be changed in the developer dashboard — it is not a permissions issue, it is simply the rule. Correct approach: Proofread name and description character by character before uploading; if there really is an error, the only option is to modify the manifest, bump the version, repackage, and upload again.
🔧 Advanced tips (optional)
- Attach the official manifest schema to your editor: Associate the manifest schema in your code editor (json.schemastore.org/chrome-manifest), so wrong fields or missing required items are flagged in red on the spot, instead of finding out only when Chrome refuses to load
version_namevs.versiondivision of labor:versionis the machine version number Chrome uses for comparison;version_name(e.g.,"1.0 beta") is a human-readable alias shown on the extensions management page. If you want "a nice-looking version name + a strictly increasing version number", write bothshort_nameto the rescue: When the name is too long and display space is insufficient,short_name(≤12 characters) steps in; if omitted, Chrome truncatesnamefor display
Lesson Summary
✅ In this lesson you nailed:
- The layered structure of manifest.json: platform-required 3 fields, store-required 2 fields, optional 41 fields (including 4 ChromeOS-specific fields)
- Building up from a minimal example to the full syntax of action, background, content_scripts, and permissions step by step
- The format rules for version and the "increment only" discipline, plus two iron rules: no comments in JSON and root directory placement
- Pre-submission checklist: metadata cannot be changed after upload, so fixing a typo = releasing a new version
Review of key configuration fields:
| Field | Recommended value / limit | Description |
|---|---|---|
manifest_version | 3 | The only supported value |
name | ≤75 characters | The extension name shown in three places |
version | start at 0.0.0.1, increment only | 1-4 dot-separated integers, each segment 0-65536 |
description | ≤132 characters | Required by the store |
icons | start with the two sizes 48 / 128 | Required by the store; the paths must exist |
action.default_popup | hello.html | The popup page shown when the icon is clicked |
background.service_worker | service-worker.js | The event hub script |
content_scripts[].matches | match pattern, required | Determines which pages get injected |
permissions | List only the APIs you use | e.g. ["storage"] |
host_permissions | An array of match patterns | Requests authorization at install time |
Next Lesson Preview
Now that the fields are declared, the next lesson C4 Content Scripts and Service Workers: the Brain and Hands of an Extension explains the mechanisms behind these two manifest fields in depth.
You will learn:
- The official definitions of content scripts and service workers, and what each can do
- "Isolated worlds": why your scripts and page variables can't see each other
- How to choose among static declaration, dynamic declaration, and programmatic injection
- The service worker's on-demand startup and idle-stop temperament, and when to use which