The manifest.json file is the contract between your Chrome extension and Chrome itself. Chrome reads it before doing anything else, and every behavior of your extension descends from what's declared here. The format is straightforward JSON, but the details are exacting: a missing field can prevent loading, a typo in a permission name silently disables functionality, and Chrome's own documentation is scattered across three different sites.
This reference documents every field in Manifest V3 with the actual format, valid values, common mistakes, and the context for when you'd actually use it. The fields are grouped by purpose, not alphabetical order — required fields first, then execution declarations, then UI, then everything else.
For the broader context of how Chrome extensions work, see the complete guide. For documentation of the chrome.* APIs your extension calls at runtime, see the APIs reference.
The minimum valid manifest
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0.0"
}
That's it. Three fields. Chrome will load this — it does nothing because no scripts or UI are declared, but it loads. Every other field below is added when your extension needs the functionality the field unlocks.
Required fields
manifest_version
Type: Integer
Required: Yes
Valid values: 3
The version of the manifest format. Must be 3. Chrome stopped accepting V2 extensions in the Web Store in 2024, and the Chrome browser itself stopped running V2 extensions in 2025 (depending on enterprise policy).
"manifest_version": 3
Do not use "3" (string) — must be the number 3.
name
Type: String Required: Yes Max length: 75 characters Localizable: Yes
The human-readable name of the extension. Shown in Chrome's extension list, in the Web Store, and in install prompts. This is the user-facing name and matters significantly for discoverability.
"name": "Save This"
For localized extensions, use the __MSG_extensionName__ pattern and define translations in _locales/[lang]/messages.json:
"name": "__MSG_extensionName__"
Naming guidelines: keep it short, avoid generic terms ("AI Assistant" is a Web Store policy violation), don't include version numbers or platform names ("for Chrome" is implied).
version
Type: String
Required: Yes
Format: Up to four dot-separated integers (e.g., 1.0.0 or 1.0.0.1)
The version of your extension. Chrome rejects same-or-lower version numbers when updating, so this must always increase. Use semver: MAJOR.MINOR.PATCH.
"version": "1.2.3"
Common mistakes:
- Using a string like
"1.0-beta"— only digits and dots are allowed - Using a leading zero like
"01.0.0"— Chrome parses as1.0.0and may reject as same version - Skipping the patch number — Chrome may parse
"1.0"ambiguously
version_name
Type: String Required: No Max length: 75 characters
An optional human-readable version string shown in the extensions UI alongside version. Useful for marketing-friendly version names like "Spring 2026 Update" while keeping version numeric.
"version": "1.2.3",
"version_name": "1.2.3 — Spring 2026 Update"
Description and identity
description
Type: String Required: For Web Store submission, no for development Max length: 132 characters Localizable: Yes
A short description of what the extension does. Shown in the Chrome extensions page, the Web Store listing summary, and install prompts. The first 132 characters of your Web Store description are derived from this.
"description": "Save any webpage to a quick-access list. Works on every site, no account required."
Writing tips:
- Lead with what the extension does, not who it's for
- Avoid superlatives ("best", "fastest") — Web Store reviewers sometimes flag these
- No emojis (rejected by some Web Store reviewers as unprofessional)
author
Type: String Required: No
The author of the extension. Mostly informational and not surfaced in most UI.
"author": "PlugThis Team"
homepage_url
Type: String (URL) Required: No
A link to your homepage. Appears in Chrome's extension UI as a "Visit website" link when users view extension details.
"homepage_url": "https://plugthis.ai"
icons
Type: Object mapping size strings to image paths Required: Required for Web Store submission Recommended sizes: 16, 32, 48, 128
Icons used throughout Chrome's UI: the extensions page, the install prompt, the Web Store listing, and the "manage extensions" view. Each size has a specific use:
- 16x16 — favicons, extension menu items
- 32x32 — Windows install confirmation
- 48x48 — extensions page
- 128x128 — Web Store listing, install dialog
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
Format requirements: PNG with alpha transparency. SVG is not supported (despite Chrome's docs hinting otherwise — it doesn't work in production).
A common production mistake is providing only one icon size. Chrome will scale it for other sizes, but the result looks blurry and unprofessional. Always provide all four.
Permissions and access
permissions
Type: Array of strings Required: No (but most extensions need at least one)
API permissions your extension requires. Each permission corresponds to a chrome.* API. Asking for permissions you don't use is a Web Store policy violation and a red flag for reviewers.
"permissions": [
"storage",
"activeTab",
"scripting",
"notifications"
]
The full set of permissions is large (50+). The most common:
storage— chrome.storage APIs for persistent dataactiveTab— temporary access to the current tab when user clicks the extensiontabs— full tabs API (read all tabs, switch, create, close)scripting— chrome.scripting for programmatic content injectionnotifications— desktop notificationscontextMenus— add items to the right-click menucookies— read/write browser cookies (for declared host_permissions)bookmarks— read/modify bookmarkshistory— read/modify browsing historydownloads— manage downloadsclipboardRead— read from clipboardclipboardWrite— write to clipboardalarms— schedule periodic taskswebNavigation— observe page navigation eventswebRequest— observe network requests (NOT block them — see declarativeNetRequest)declarativeNetRequest— rules-based request blocking/modificationidentity— OAuth flow for Google accountsoffscreen— offscreen document API for DOM access in background contextsunlimitedStorage— bypass the default chrome.storage.local quota
Each permission has implications for the Web Store install prompt. Users see human-readable warnings like "Read your browsing history" (for tabs) or "Modify data you copy and paste" (for clipboardWrite). Higher-risk permissions reduce install rates.
optional_permissions
Type: Array of strings Required: No
Permissions your extension can request at runtime but doesn't require at install time. The extension asks for these via chrome.permissions.request() only when needed.
"optional_permissions": [
"history",
"bookmarks"
]
Best practice: use optional permissions for features that are nice-to-have but not core. The extension installs cleanly with the minimum permission set, and users grant additional permissions only if they actually use the relevant features. This significantly improves install rates.
host_permissions
Type: Array of URL match patterns Required: No (required for content_scripts and cross-origin fetches in most cases)
URL patterns where your extension can run content scripts, make cross-origin network requests, and intercept network traffic.
"host_permissions": [
"https://*.example.com/*",
"https://api.openai.com/*"
]
URL match pattern syntax:
<all_urls>— every URL (heaviest review burden)*://*/*— every URL on http or httpshttps://*/*— every HTTPS URLhttps://example.com/*— specific domain, any pathhttps://*.example.com/*— domain and all subdomainshttps://example.com/specific-path/*— specific domain and path prefix
Common mistakes:
- Using
*alone instead of<all_urls>(rejected as invalid) - Forgetting the trailing
/*(https://example.comwon't match — needs to behttps://example.com/*) - Using
http://when you only need HTTPS (broadens permission unnecessarily)
A note on Web Store review: <all_urls> triggers the strictest review level. If your extension only needs to work on a few specific sites, list them explicitly. Reviewers consistently push back on broad host permissions where narrower ones would work.
optional_host_permissions
Type: Array of URL match patterns Required: No
Host permissions your extension can request at runtime. Same syntax as host_permissions, but the extension installs without them and prompts the user when needed.
"optional_host_permissions": [
"https://*.example.com/*"
]
Useful for extensions that work on a core set of sites by default but optionally expand. Users who only need the core experience get a cleaner install prompt.
externally_connectable
Type: Object Required: No
Declares which other extensions or websites can send messages to your extension via chrome.runtime.sendMessage. Default: only other parts of your own extension.
"externally_connectable": {
"matches": ["https://plugthis.ai/*"],
"ids": ["abcdef0123456789abcdef0123456789"]
}
matches allows websites at those URLs to message your extension. ids allows specific other extensions (by their Chrome Web Store ID) to message yours. Use this only when you need a deliberate cross-extension or web-to-extension integration.
Execution declarations
background
Type: Object Required: No
Declares the background service worker. In Manifest V3, there's no persistent background page — only a service worker that Chrome can terminate and restart as needed.
"background": {
"service_worker": "background.js",
"type": "module"
}
service_worker is the path to your background script, relative to the extension root. The path must point to an existing file.
type is optional. Set to "module" if your background script uses ES modules (import/export). If omitted, the script runs as a classic script and import won't work.
Common confusion: there's no persistent flag anymore. In V2 you could declare a persistent background page that never died. V3 service workers always have a lifecycle controlled by Chrome — they wake on events, run, and idle out. You cannot make them persistent. Adapt your code to be event-driven.
content_scripts
Type: Array of content script declarations Required: No
Scripts and stylesheets to inject into webpages matching specified URL patterns.
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"exclude_matches": ["https://example.com/admin/*"],
"js": ["content.js"],
"css": ["content.css"],
"run_at": "document_idle",
"all_frames": false,
"match_about_blank": false,
"world": "ISOLATED"
}
]
Each entry's fields:
matches— required. URL patterns where the script injects. Same syntax ashost_permissions.exclude_matches— optional. URL patterns to exclude frommatches.js— optional. Array of script paths. Scripts inject in order.css— optional. Array of stylesheet paths.run_at— optional. When to inject. Values:document_start(before any DOM),document_end(after DOM is parsed, before resources load),document_idle(default — after page is mostly idle).all_frames— optional, default false. If true, injects into all iframes too.match_about_blank— optional, default false. If true, injects into about:blank pages opened from matched origins.world— optional. EitherISOLATED(default — sandboxed from page scripts) orMAIN(runs in the page's own JavaScript context, can interact with page variables).
Multiple content script entries are allowed. Common pattern: one entry for the main extension behavior, another for specific sites with custom logic.
Gotchas:
- Content scripts have limited Chrome API access. They can use
chrome.storage,chrome.runtime(for messaging), and a few others. Most APIs (likechrome.tabs) are only available in the background worker. - The
world: "MAIN"mode is powerful but rarely needed. Use it only when you need to call functions defined by the page's own scripts. - Content scripts run on a per-page basis. If your script keeps state in a global variable, that state is per-page, not shared across tabs.
action
Type: Object Required: Recommended for any extension with a toolbar presence
Declares the extension's toolbar icon and what happens when the user clicks it.
"action": {
"default_title": "Save This",
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
}
default_title — the tooltip shown when hovering the icon
default_popup — path to an HTML file that opens when the user clicks the icon. If omitted, the click fires chrome.action.onClicked in the background worker instead.
default_icon — the toolbar icon, in multiple sizes
If you don't declare default_popup, the toolbar icon click sends an event to the background worker, where you can handle it programmatically (open a new tab, run a script, send a notification, etc.).
A common pattern: declare a popup for most users, but enable a "quick action" mode where the icon directly triggers behavior. To switch modes at runtime, use chrome.action.setPopup({ popup: '' }) to disable the popup and use chrome.action.onClicked instead.
options_ui
Type: Object Required: No
Declares an options/settings page accessible from the extensions list.
"options_ui": {
"page": "options.html",
"open_in_tab": true
}
page — path to the HTML file
open_in_tab — if true, opens in a full tab. If false (default), opens in a modal-style embedded panel that's smaller and more constrained.
Strong recommendation: always use open_in_tab: true unless your settings are extremely minimal. The embedded panel is too small for serious settings UI.
chrome_url_overrides
Type: Object Required: No
Lets your extension replace one of Chrome's built-in pages.
"chrome_url_overrides": {
"newtab": "newtab.html"
}
Available overrides:
newtab— replaces the new-tab pagebookmarks— replaces the bookmarks managerhistory— replaces the browsing history page
Only one extension can override each page at a time. If the user has multiple extensions trying to override the same page, Chrome picks one (usually the most recently installed) and shows a warning.
This is a high-friction permission with users — overriding newtab is the most common and is what every "productivity new tab" extension does. Be sure the override adds clear value or users will uninstall quickly.
commands
Type: Object mapping command names to definitions Required: No
Declares keyboard shortcuts the user can configure.
"commands": {
"save-page": {
"suggested_key": {
"default": "Ctrl+Shift+S",
"mac": "Command+Shift+S"
},
"description": "Save the current page",
"global": false
},
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+E"
}
}
}
suggested_key — default keybinding (users can change it in chrome://extensions/shortcuts)
description — shown in Chrome's shortcuts UI
global — if true, the shortcut works even when Chrome isn't focused
The special command _execute_action triggers the extension's action (opens the popup or fires onClicked). Other named commands fire chrome.commands.onCommand in the background worker.
Limit: Chrome allows at most 4 suggested keys per extension (Chrome enforces this at install — extensions with more get the extras unconfigured by default). Users can manually add more shortcuts in chrome://extensions/shortcuts.
omnibox
Type: Object Required: No
Registers a custom keyword for Chrome's address bar. When the user types the keyword followed by a space, the rest of what they type is sent to your extension as a search query.
"omnibox": {
"keyword": "save"
}
After this, typing save my-query in the address bar fires chrome.omnibox.onInputChanged and chrome.omnibox.onInputEntered in your background worker. Useful for power-user extensions that act as search interfaces or command palettes.
Web-accessible resources
web_accessible_resources
Type: Array of resource declarations Required: No (required to expose any extension files to webpages or other extensions)
Declares which files in your extension can be loaded by webpages or other extensions. By default, all extension files are private — webpages cannot embed them as images, scripts, or via fetch. Each entry whitelists specific files for specific origins.
"web_accessible_resources": [
{
"resources": ["images/overlay.png", "fonts/*.woff2"],
"matches": ["https://*.example.com/*"]
},
{
"resources": ["injected-script.js"],
"matches": ["<all_urls>"],
"use_dynamic_url": true
}
]
Each entry has:
resources— array of file paths (supports wildcards)matches— URL patterns of pages that can load these resourcesextension_ids— optional, array of other extension IDs that can load these resourcesuse_dynamic_url— optional. If true, the resource URL includes a per-session random token, making it harder for sites to fingerprint your extension's installation
Common use case: a content script that needs to inject an image or stylesheet from your extension into the page. The page can only load extension resources that are listed here.
Less common but important: injecting a script into the page's own world: "MAIN" context using chrome.scripting.executeScript from a content script requires the injected file to be web-accessible.
Networking and data
content_security_policy
Type: Object Required: No (Chrome has a strict default)
Customizes the Content Security Policy for different parts of your extension. Manifest V3 has a strict default CSP that blocks remote code execution; you can sometimes loosen it for specific resources but never to the point of allowing eval or remote scripts.
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'",
"sandbox": "sandbox allow-scripts; script-src 'self' https://example.com"
}
extension_pages — CSP for your popup, options, and other extension HTML pages
sandbox — CSP for sandboxed pages (declared separately via sandbox field)
Almost all extensions can leave this field unset. Custom CSP is only needed for unusual cases like loading remote configuration JSON or running third-party libraries that need specific allowances.
Important: in V3 you cannot use 'unsafe-eval', cannot load remote scripts, and cannot use 'unsafe-inline' for scripts. These are hard restrictions, not defaults you can override. If your extension needs to run remote code, redesign it.
declarative_net_request
Type: Object Required: No
Declares static rules for blocking, redirecting, or modifying network requests. This is the V3 replacement for blocking webRequest.
"declarative_net_request": {
"rule_resources": [
{
"id": "block_ads",
"enabled": true,
"path": "rules/ad-rules.json"
}
]
}
Each rule resource references a JSON file containing the actual rules. Rules can:
- Block matching requests
- Redirect matching requests
- Modify request or response headers
- Upgrade insecure requests
Rule limits: up to 30,000 static rules per ruleset, 100 rulesets total per extension, 5,000 dynamic rules at runtime. These limits are why content blockers complained about V3.
Most extensions don't need this. It's primarily for ad blockers, tracker blockers, privacy tools, and certain security extensions.
sandbox
Type: Object Required: No
Declares HTML pages that run in a sandboxed origin without Chrome extension privileges. Used for safely running third-party libraries or untrusted code (like user-provided JavaScript expressions).
"sandbox": {
"pages": ["sandboxed-eval.html"]
}
Sandboxed pages cannot access chrome.* APIs and run in a unique origin. They communicate with the rest of your extension via postMessage. Useful for extensions that evaluate user-defined expressions (calculators, formula parsers, query builders).
offscreen
The offscreen API isn't declared in the manifest — it's used at runtime via chrome.offscreen.createDocument(). But it requires the offscreen permission.
Useful when your background worker needs DOM access (parsing HTML, working with audio/video, certain crypto operations). The worker creates an offscreen document, communicates with it via messaging, and tears it down when done.
Extension identity
key
Type: String (base64-encoded public key) Required: No (auto-assigned by Chrome on Web Store publish)
Sets a fixed extension ID for development. Without this, your extension gets a randomly-generated ID each time you load it as unpacked, which breaks anything that depends on the ID (like externally_connectable allowlists or OAuth client IDs registered to a specific ID).
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
How to generate one: pack your extension once via chrome://extensions/ > "Pack extension". This creates a .crx and a .pem file. Run a small Node script (or use online tools) to convert the .pem to a manifest key. The resulting key locks your unpacked extension to a specific ID.
Most extensions don't need this — but if you're integrating with services that whitelist your extension ID (OAuth, externally_connectable), you'll need it.
minimum_chrome_version
Type: String (Chrome version like "100.0") Required: No
The minimum Chrome version required to run your extension. Useful if you depend on a recently-added API.
"minimum_chrome_version": "108.0"
Users on older versions see a clear "your browser is too old" message instead of cryptic errors.
update_url
Type: String (URL) Required: No (auto-set by Chrome Web Store)
The URL Chrome polls to check for extension updates. Set automatically by the Chrome Web Store when you publish there. Custom update URLs are used for self-hosted extensions (mostly enterprise).
"update_url": "https://your-update-server.com/updates.xml"
Self-hosting updates is rarely worth the complexity. Use the Chrome Web Store unless you have a specific enterprise reason not to.
Internationalization
default_locale
Type: String (locale code) Required: Required if using internationalization
The default locale for __MSG_*__ placeholders. Used when the user's preferred language isn't available.
"default_locale": "en"
Then create _locales/en/messages.json:
{
"extensionName": {
"message": "Save This",
"description": "The name of the extension."
},
"extensionDescription": {
"message": "Save any webpage to a quick-access list."
}
}
And use the placeholders in manifest fields:
"name": "__MSG_extensionName__",
"description": "__MSG_extensionDescription__"
For each additional language, add _locales/[code]/messages.json. Common codes: en, es, fr, de, ja, zh_CN, pt_BR.
A translation strategy that works: ship in English only initially. Once you have users in other markets (visible in Chrome Web Store analytics), translate. Premature internationalization adds maintenance burden for minimal install lift.
URL match pattern syntax
URL match patterns appear in content_scripts.matches, host_permissions, externally_connectable.matches, and several other places. They have specific syntax that's worth understanding fully.
Pattern structure
A URL pattern has three parts: scheme, host, and path.
<scheme>://<host><path>
- Scheme —
http,https,file,ftp, or*(which matches http or https) - Host — exact hostname, or
*(anything), or*.example.com(subdomain wildcard) - Path — exact path, or path with wildcards
Examples
| Pattern | Matches | Doesn't match |
|---|---|---|
https://example.com/* | https://example.com/anything | http://example.com/anything (wrong scheme) |
*://example.com/* | http://example.com/x, https://example.com/y | ftp://example.com/x |
https://*.example.com/* | https://a.example.com/x, https://b.example.com/x | https://example.com/x (no subdomain) |
https://*/* | every HTTPS URL | http URLs |
<all_urls> | every URL of every scheme | none |
Special tokens
<all_urls>— equivalent to*://*/*plusfile://and others. Most permissive.*in the host — matches any subdomain (and the root domain if combined with*.)*in the path — matches any path*in the scheme — matches http or https only
Common mistakes
Missing trailing /* in path. https://example.com is not a valid pattern — paths must include something. Use https://example.com/* to match all paths.
Using * to mean a different scheme. *://example.com/* matches http and https only, not other schemes like file or ftp.
Trying to match query strings. Patterns don't match query strings or fragments. https://example.com/?foo=bar is matched by https://example.com/* regardless of the query.
Wildcards in the middle of paths. https://example.com/users/*/profile works as you'd expect. Wildcards in arbitrary parts of a path are fine.
Subdomain wildcard at the root. https://*.com/* is rejected — too broad. *.com is not a valid wildcard target. Use https://*/* for "every HTTPS site."
Complete example — a real-world manifest
Here's a manifest for a hypothetical "Page Annotator" extension that highlights selected text and saves notes:
{
"manifest_version": 3,
"name": "Page Annotator",
"version": "1.4.2",
"version_name": "1.4.2 — Improved sync",
"description": "Highlight and annotate any webpage. Notes sync across devices.",
"author": "Acme Annotation Co.",
"homepage_url": "https://annotator.acme.com",
"default_locale": "en",
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
},
"permissions": [
"storage",
"activeTab",
"scripting",
"contextMenus",
"notifications"
],
"optional_permissions": [
"history"
],
"host_permissions": [
"https://*/*"
],
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"exclude_matches": ["https://*.bank-of-example.com/*"],
"js": ["content/main.js"],
"css": ["content/highlight.css"],
"run_at": "document_idle"
}
],
"action": {
"default_title": "Page Annotator",
"default_popup": "popup/popup.html",
"default_icon": {
"16": "icons/icon-16.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
}
},
"options_ui": {
"page": "options/options.html",
"open_in_tab": true
},
"commands": {
"toggle-highlight": {
"suggested_key": {
"default": "Ctrl+Shift+H",
"mac": "Command+Shift+H"
},
"description": "Toggle highlight on current selection"
},
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+A",
"mac": "Command+Shift+A"
}
}
},
"web_accessible_resources": [
{
"resources": ["fonts/*.woff2", "icons/cursor.svg"],
"matches": ["<all_urls>"]
}
],
"minimum_chrome_version": "108.0"
}
This is a realistic manifest for a real extension. Note the patterns: minimum necessary permissions, narrow host permissions where possible, content scripts excluded from bank URLs, options page opens in a tab, two keyboard shortcuts, web-accessible fonts for the highlight UI.
Field deprecation notes
Several fields from Manifest V2 are gone in V3. If you're migrating from V2 docs or examples, watch for:
background.scripts— replaced bybackground.service_worker. Persistent background pages no longer exist.background.persistent— removed. Background is always a service worker now.browser_actionandpage_action— both replaced by a singleactionfield.web_accessible_resourcesas an array of strings — must now be an array of objects withresourcesandmatches.content_security_policyas a string — must now be an object withextension_pagesand/orsandbox.permissionswith host patterns — host patterns now go inhost_permissions, notpermissions. The old V2 style is silently ignored in V3.webRequestblocking — still allowed for observation but not for blocking. UsedeclarativeNetRequestto block.
If you find old extension tutorials online, check the date or the manifest version they show. V2 examples will not run as-is in V3.
Testing your manifest
Two free tools that catch most manifest issues:
Chrome's own loader. Load your extension as unpacked. Chrome shows specific error messages for invalid manifest fields. The error text is usually the fastest path to diagnosis.
JSONLint or a similar JSON validator. If your manifest won't parse at all, it's likely a JSON syntax issue (trailing comma, missing quote). JSONLint highlights the exact line.
For more advanced validation, the Chrome team maintains a JSON Schema for Manifest V3 at github.com/GoogleChrome/chrome-extensions-samples. Some IDEs can validate against it for live error checking as you type.
A useful debugging habit: keep a known-good minimal manifest in a separate file. When something breaks, compare your manifest to the minimum and add fields back one at a time until you find what introduced the issue.
What this reference doesn't cover
Two intentional omissions:
Native messaging. Chrome extensions can communicate with native applications via Native Messaging hosts. The manifest fields involved (nativeMessaging permission) are documented in Chrome's official docs. This is a niche feature used by password managers, IDEs, and a few security tools.
Chrome Apps fields. Chrome Apps (a separate extension category) was deprecated in 2018 and removed in 2022. Any manifest fields specific to Chrome Apps (app.background, kiosk_enabled, etc.) no longer work. If you see them in old documentation, ignore them.
Next steps
If you're writing extension code that calls the APIs declared in your manifest, see the Chrome extension APIs reference — every chrome.* API documented with examples.
If you're building your first Chrome extension and want the whole picture from idea to Web Store launch, the complete guide is the structured walkthrough.
And if writing the manifest by hand feels tedious, PlugThis generates the full manifest for you based on a plain-English description of what you want the extension to do. The output is exactly the kind of clean V3 manifest documented here.