C.W.K.
Stream
Lesson 02 of 06 · published

Anatomy of manifest.json

~12 min · mv3, manifest, schema, clipdeck, hands-on

Level 0Extension Curious
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"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_locale live 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 be 3 as 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.json i18n. Skip until you need it.

Surface-Conditional — The Big Ones

  • action — toolbar icon + popup configuration. default_popup points 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.

Three required, three recommended, the rest conditional. If you remember nothing else about manifest.json, remember those three tiers — and that every field you add is a promise to use the surface it declares.
Silent failures live in surface-conditional fields. A typo in "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.

Code

Three-field minimum that Chrome will accept·json
{
  "manifest_version": 3,
  "name": "Bare Minimum",
  "version": "0.1.0"
}
ClipDeck Track-1 hello-world manifest.json (save under clipdeck/)·json
{
  "manifest_version": 3,
  "name": "ClipDeck",
  "version": "0.1.0",
  "description": "Selected-text clipboard you can CRUD from any page.",
  "icons": {
    "16": "icons/icon-16.png",
    "48": "icons/icon-48.png",
    "128": "icons/icon-128.png"
  },
  "action": {
    "default_popup": "popup.html",
    "default_title": "ClipDeck"
  }
}

External links

Exercise

Create a directory called clipdeck/ somewhere convenient (Desktop, ~/dev, wherever you keep small projects). Save the ClipDeck hello-world manifest from this lesson as clipdeck/manifest.json. Then make three placeholder PNG icons at icons/icon-16.png, icons/icon-48.png, icons/icon-128.png — a flat color square is fine; transparent PNGs are fine; any image editor (Preview, Photoshop, even an online tool) works. Do not skip the icons — Chrome will load without them but warn, and you want a clean load. The popup.html itself is lesson 4's job; for now this is purely the manifest scaffold.
Hint
If you have ImageMagick installed, three flat-color icons take one line: for s in 16 48 128; do magick -size ${s}x${s} xc:steelblue icons/icon-${s}.png; done. macOS Preview's File → Export → PNG at fixed pixel dimensions also works. The point is to have valid PNG files at those exact paths so manifest.json's icons field resolves cleanly.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Pippawarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.