"manifest.json is the extension's birth certificate. It's the very first thing Chrome reads, and the very first thing Chrome rejects you for getting wrong."
The Three-Field Manifest
Chrome will accept a manifest with exactly three fields. It won't do anything useful, but it will load:
That's the floor. Everything else — popups, side panels, background workers, content scripts — is added on top of these three required fields. Knowing this floor is what keeps you from cargo-culting fifteen fields you don't actually need.
The Three Tiers of Fields
Think of manifest fields in three layers:
- Required — Chrome refuses to load the extension without them. Just three:
manifest_version,name,version. - Strongly recommended — Chrome loads without them, but the user experience suffers.
description,icons,default_localelive here. - Surface-conditional — only meaningful if you use the surface they declare.
action(toolbar icon + popup),background(service worker),content_scripts,side_panel,permissions,host_permissions,content_security_policy,options_ui,commands,web_accessible_resources, and a dozen others.
Adding a surface-conditional field without using the surface is harmless but noisy. Forgetting one when you do use the surface is a silent failure — the extension loads, but nothing happens. That class of bug is half of MV3's debugging stories.
The Required Three, in Detail
manifest_version: must be3as of 2024. Number, not string.name: 1-75 characters, shown in chrome://extensions and on the toolbar hover.version: 1-4 dot-separated integers."0.1.0","1.2","3"are all valid. Chrome compares versions numerically for updates.
Strongly Recommended
description: 1-132 characters, the one-liner shown beneath the name.icons: an object mapping sizes (16, 32, 48, 128) to PNG paths. Chrome falls back gracefully if you skip some sizes, but supplying 16 + 48 + 128 covers every UI surface.default_locale: enables_locales/*/messages.jsoni18n. Skip until you need it.
Surface-Conditional — The Big Ones
action— toolbar icon + popup configuration.default_popuppoints at the HTML file Chrome renders when the icon is clicked.background— declares the service worker file. MV3-shaped:{ "service_worker": "background.js" }.content_scripts— array of injection rules.matches(URL patterns),js(script files),run_at(document_start|document_end|document_idle),all_frames(boolean).side_panel— points at the HTML loaded inside Chrome's side panel surface (Chrome 114+).permissions— API capabilities (storage,tabs,activeTab,scripting,sidePanel, etc.).host_permissions— URL patterns the extension may read/inject into. Separate from API permissions for clarity.content_security_policy— fine-tunes what extension pages may load. The default is already tight; only override if you know what you're loosening or tightening.
ClipDeck's Hello-World Manifest
For Track 1, ClipDeck needs five things: a name, a version, a manifest version, an icon for the toolbar, and a popup. That's it. No service worker, no content script, no side panel, no permissions — Track 1 is hello-world.
Save the manifest below to clipdeck/manifest.json. The popup HTML and JS come in lesson 4 (hello-clipdeck); icons can be any 16×16 / 48×48 / 128×128 PNGs. A flat color square works fine for now.
"service_worker" still loads. A missing matches in a content script still loads. Chrome's strictness is upfront on required fields and lenient on optional ones — debugging means knowing which silence is suspicious.