Web App Manifest Cheat Sheet¶
This page condenses the web app manifest into one scannable reference: every member with its JSON type, default, effect on installability and browser support, a minimal and a fully annotated complete manifest, the legacy <meta> and <link> tags each member replaces, and every enumerated value you can type into the file. Use it when you are writing or reviewing a manifest and need the exact spelling, shape or constraint of a member right now. The full processing rules, error messages and gotchas behind each row live in the Members Reference; this page links there instead of repeating them.
Key takeaways
- For Chromium's install criteria you need
nameorshort_name, a parsedstart_url, an effective display mode (the firstdisplay_overrideentry Chromium recognizes, otherwisedisplay) ofstandalone,fullscreenorminimal-ui, orwindow-controls-overlayortabbedwhen it comes fromdisplay_override, and oneany-purpose PNG, SVG or WebP icon of at least 144 px withsizesdeclared. Safari and Firefox have no manifest requirements at all. - Always set
id,start_urlandscopeexplicitly, with root-relative paths.idis permanent: a differentidis a different app. - URL members resolve against the manifest URL;
idresolves against the origin ofstart_url. Every navigational URL must be same-origin and usually insidescope. - Invalid values are ignored, never coerced:
"false"is notfalse, andwindow-controls-overlayindisplayis dropped, which leaves the manifest without a validdisplayand not installable. - Keep
<link rel="apple-touch-icon">and<meta name="theme-color">: Safari prefers the former over manifest icons, and the latter is the only working dark-mode theme color today. - Integration members (
share_target,file_handlers,protocol_handlers,launch_handler,scope_extensions) are Chromium-only; Safari and Firefox ignore them safely.
Serving and linking the manifest¶
| Item | Value | Why it matters |
|---|---|---|
| File name | Anything; app.webmanifest or manifest.webmanifest by convention | Browsers only follow the <link>; the name has no meaning |
| MIME type | application/manifest+json (any JSON MIME type is acceptable) | Chromium does not check it, but CDNs and validators do |
| Link element | <link rel="manifest" href="/app.webmanifest"> in the <head> of every page that should be installable | A page without the link is not installable from that page |
| Credentials | Fetched in CORS mode without cookies; add crossorigin="use-credentials" only if the manifest URL requires authentication | A cookie-protected manifest returns a login page that fails to parse |
| Cross-origin manifest | Needs Access-Control-Allow-Origin; relative URLs inside resolve against the manifest's origin | A CDN-hosted manifest breaks start_url, scope and every in-scope URL member |
| CSP | manifest-src (falls back to default-src) must allow the manifest URL | A blocked manifest fetch looks like "no manifest" |
| Caching | Short or revalidated cache lifetime (Cache-Control: no-cache or a few minutes) | Browsers re-fetch it to detect updates |
| JSON | Strict JSON: no comments, no trailing commas | A syntax error yields an empty manifest and the page is not installable |
| One app per manifest | Keep id constant across every variant (locale, tenant, environment) | A document ignores a later manifest with a different id |
Serving details, dynamic manifests and the fetch pipeline are covered in Web App Manifest.
Minimal installable manifest¶
The smallest manifest that passes Chromium's installability check on desktop and Android, is honored by Safari on iOS, iPadOS and macOS, and by Firefox for Android:
{
"name": "Pocket Notes",
"start_url": "/",
"display": "standalone",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
It works, but it leaves the app's identity tied to start_url, has no maskable icon (Android launchers will shrink the icon onto a white disc), no theme or splash colors, and no screenshots for Chrome's richer install dialog. Add these seven lines before you ship:
{
"id": "/",
"name": "Pocket Notes",
"short_name": "Notes",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"background_color": "#f7f4ed",
"theme_color": "#1f6f5c",
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
What each browser needs before it will install or promote:
| Browser | Manifest requirement | What happens without it |
|---|---|---|
| Chrome, Edge, Samsung Internet (Chromium) | name or short_name; parsed start_url (desktop); display/display_override resolving to standalone, fullscreen or minimal-ui (or window-controls-overlay or tabbed, from display_override only); one any icon ≥ 144 px (PNG, SVG or WebP, with sizes; ≤ 1024 px on desktop); prefer_related_applications not true | No beforeinstallprompt, no address-bar install icon; users can still use Install page as app |
| Safari on iOS and iPadOS 26+ | None | Every Home Screen site opens as a web app unless the user turns off Open as Web App |
| Safari on macOS 14+ | None | Add to Dock works for any site; the manifest only customizes the app |
| Firefox for Android | None formally; a manifest improves name, icon and display | Home-screen shortcut built from page metadata |
| Firefox on Windows (143+) | None | Sites can be pinned to the taskbar as web apps (Microsoft Store build from 150); no manifest-based install. Linux: disabled by default behind browser.taskbarTabs.enabled; not on macOS |
The criteria, error codes and history of the service-worker requirement are explained in Installability Criteria.
All members at a glance¶
Legend for Install: Required means Chromium's installability check fails without it; Recommended means installation works but the installed app is visibly worse; Blocks means a value that prevents promotion; — means no effect on installability.
Core members (W3C Web Application Manifest)¶
| Member | Type | Default when absent | Install | Acted on by | Details |
|---|---|---|---|---|---|
name | string | None | Required (or short_name) | All engines with manifest support | name |
short_name | string | None | Required (or name) | All engines with manifest support | short_name |
id | string (URL) | Processed start_url without fragment | Recommended | Chromium 96+, Safari 16.4+ (iOS), Safari 17+ (macOS) | App Identity |
start_url | string (URL) | The document URL | Required (desktop Chromium) | All engines with manifest support | start_url |
scope | string (URL) | Directory of start_url | Recommended | All engines with manifest support | scope |
display | string | browser | Required (not browser) | All; Safari only standalone/browser | Display Modes |
icons | array of image resources | Empty list | Required | All; Safari only when no apple-touch-icon | Icons |
theme_color | string (CSS color) | None | Recommended | Chromium, Safari 15+ (iOS), Safari 17+ (macOS), Firefox for Android | theme_color |
background_color | string (CSS color) | None | Recommended | Chromium, Firefox for Android | background_color |
orientation | string | None (device behavior) | — | Android browsers | orientation |
shortcuts | array of shortcut items | Empty list | — | Chromium, Safari 17.4+ (macOS) | App Shortcuts |
lang | string (BCP 47 tag) | Unknown language | — | Limited; fallback language for *_localized in Chrome 148+ | lang |
dir | string | auto | — | Limited; Chromium uses it for shortcut text | dir |
name_localized, short_name_localized, icons_localized | object (language map) | None | — | Chrome and Edge 148+ on desktop | *_localized |
color_scheme_dark | object | None | — | No browser yet | color_scheme_dark |
Application information members (W3C Group Note)¶
| Member | Type | Default | Install | Acted on by | Details |
|---|---|---|---|---|---|
description | string | None | — | Chromium (install dialogs, desktop app metadata) | description |
screenshots | array of screenshot objects | Empty list | Recommended (richer install UI) | Chrome 94+ Android, Chrome 108+ desktop, stores | Rich Install UI |
categories | array of strings | Empty list | — | Safari 17.4+ on macOS (Launchpad folder names), stores | categories |
iarc_rating_id | string | None | — | Stores and packaging tools only | iarc_rating_id |
Integration and incubation members (Chromium)¶
| Member | Type | Default | Install | Acted on by | Details |
|---|---|---|---|---|---|
display_override | array of strings | Empty list | Its first recognized entry replaces display in the check | Chromium 89+ | Display Modes |
launch_handler | object (client_mode) | { "client_mode": "auto" } | — | Chromium 110+ desktop (MDN also lists Chrome for Android 110 and Samsung Internet 21.0, where launchQueue is unavailable) | launch_handler |
share_target | object | None | — | Chrome for Android 76+, ChromeOS, Samsung Internet | Web Share Target |
file_handlers | array | Empty list | — | Chromium 102+ desktop | File Handling |
protocol_handlers | array | Empty list | — | Chromium 96+ desktop | Protocol Handlers |
scope_extensions | array | Empty list | — | Chrome and Edge 139+ desktop | scope_extensions |
migrate_from, migrate_to | array, object | None | — | Chrome 150+ desktop (same-site only) | App Identity |
tab_strip | object | New tab opens start_url, no home tab | — | ChromeOS (with tabbed) | tab_strip |
note_taking | object (new_note_url) | None | — | ChromeOS | note_taking |
related_applications | array | Empty list | — | Chromium (native-app promotion, getInstalledRelatedApps()) | Detecting Installed Apps |
prefer_related_applications | boolean | false | Blocks when true on Android; on desktop only with a chrome_web_store entry (or play on ChromeOS with Android apps) | Chromium | prefer_related_applications |
handle_links | string | None | — | No browser (proposal) | Advanced Members |
Vendor-specific and legacy members¶
| Member | Type | Status | Recommendation |
|---|---|---|---|
edge_side_panel | object (preferred_width) | Microsoft Edge; Microsoft's documentation marks it as being deprecated | Don't build new features on it |
widgets | array | Microsoft Edge on Windows 11 Widgets Board | Only if you target that surface |
serviceworker | object (src, scope, use_cache) | Chromium, for just-in-time payment-handler installs only | Not used for PWA installation; see Payments |
gcm_sender_id | string | Obsolete (pre-VAPID Chrome push) | Remove it |
translations | object | Behind a Chromium flag; superseded by *_localized | Don't use |
Support matrix¶
Support data as of September 2026. ✅ supported (first version), ❌ not supported, ⚠️ partial (see notes below the table). For live data see MDN's manifest reference and caniuse.com.
| Member | Chrome / Edge desktop | Chrome Android | Safari macOS | Safari iOS / iPadOS | Firefox Android | Samsung Internet |
|---|---|---|---|---|---|---|
name, short_name, start_url | ✅ 39 | ✅ 39 | ✅ 17 | ✅ 11.3 | ✅ 79 | ✅ 4.0 |
scope | ✅ 53 | ✅ 53 | ✅ 17 | ✅ 11.3 | ✅ 79 | ✅ 6.0 |
id | ✅ 96 | ✅ 96 | ✅ 17 | ✅ 16.4 | ❌ | ✅ 17.0 |
display | ✅ 39 | ✅ 39 | ⚠️ 17 | ⚠️ 11.3 | ✅ 47 | ✅ 4.0 |
display_override | ✅ 89 | ✅ 89 | ❌ | ❌ | ❌ | ✅ 15.0 |
icons | ✅ 39 | ✅ 39 | ⚠️ 17 | ⚠️ 15.4 | ✅ 79 | ✅ 4.0 |
theme_color | ✅ 46 | ✅ 46 | ✅ 17 | ✅ 15 | ✅ 79 | ✅ 5.0 |
background_color | ✅ 46 | ✅ 46 | ❌ | ❌ | ✅ 79 | ✅ 5.0 |
orientation | ⚠️ | ✅ 39 | ❌ | ❌ | ✅ 79 | ✅ 4.0 |
description | ✅ 88 | ✅ 88 | ❌ | ❌ | ❌ | ✅ 15.0 |
screenshots | ✅ 108 | ✅ 94 | ❌ | ❌ | ❌ | ⚠️ |
shortcuts | ✅ 96 | ✅ 84 | ✅ 17.4 | ❌ | ❌ | ✅ 14.0 |
categories | ❌ | ❌ | ✅ 17.4 | ❌ | ❌ | ❌ |
*_localized | ✅ 148 | ❌ | ❌ | ❌ | ❌ | ❌ |
launch_handler | ✅ 110 | ⚠️ 110 | ❌ | ❌ | ❌ | ⚠️ 21.0 |
share_target | ⚠️ 89 | ✅ 76 | ❌ | ❌ | ❌ | ✅ 12.0 |
file_handlers | ✅ 102 | ❌ | ❌ | ❌ | ❌ | ❌ |
protocol_handlers | ✅ 96 | ❌ | ❌ | ❌ | ❌ | ❌ |
scope_extensions | ✅ 139 | ❌ | ❌ | ❌ | ❌ | ❌ |
migrate_from, migrate_to | ✅ 150 | ❌ | ❌ | ❌ | ❌ | ❌ |
related_applications, prefer_related_applications | ⚠️ | ✅ 44 | ❌ | ❌ | ❌ | ✅ 4.0 |
color_scheme_dark | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
lang, dir | ⚠️ | ❌ | ❌ | ❌ | ❌ | ❌ |
tab_strip | ⚠️ 126 | ❌ | ❌ | ❌ | ❌ | ❌ |
note_taking | ⚠️ 95 | ❌ | ❌ | ❌ | ❌ | ❌ |
Notes on the ⚠️ cells: Safari honors only standalone and browser for display (fullscreen opens as standalone, minimal-ui as a browser tab before iOS 26). Safari uses manifest icons only when the page has no apple-touch-icon and only icons whose purpose is any or absent. Desktop Chromium parses orientation, but app windows don't rotate. window.launchQueue is not available on Android (Chrome or Samsung Internet, whose 21.0 entry MDN derives from Chromium 110), so launch_handler there has no script-visible effect. On desktop, share_target registers only on ChromeOS. Desktop browsers mostly ignore prefer_related_applications, except that desktop Chromium suppresses install promotion when related_applications lists a chrome_web_store entry (or a play entry on ChromeOS with Android apps); desktop Chromium also uses related_applications for getInstalledRelatedApps() (installed web apps from Chrome 140, Windows apps from 85). MDN has no data for screenshots in Samsung Internet. Chrome and Edge 85 to 95 supported shortcuts on Windows only. Chromium parses lang and dir: dir applies to shortcut text and lang is the fallback language of *_localized entries (148+). tab_strip shipped on ChromeOS only and is behind flags on other desktops; note_taking is parsed everywhere but used only on ChromeOS. Firefox on Windows (143+) reads a manifest, when present, for details such as the taskbar icon, but MDN's data records no member support there.
A fully annotated complete manifest¶
Every member below is valid and processed by at least one shipping browser, except where an annotation says otherwise. Click the numbered markers for the rule behind each line. The JSON stays valid when you copy it: the annotation comments are stripped from the copied text.
{
"id": "/", // (1)!
"name": "Acme Tasks: Team Planner", // (2)!
"short_name": "Tasks", // (3)!
"description": "Plan, assign and track team tasks, online or offline.", // (4)!
"lang": "en-US", // (5)!
"dir": "ltr", // (6)!
"name_localized": { // (7)!
"de": "Acme Aufgaben: Teamplaner",
"fr": { "value": "Acme Tâches : planificateur", "lang": "fr" }
},
"start_url": "/?source=pwa", // (8)!
"scope": "/", // (9)!
"display": "standalone", // (10)!
"display_override": ["window-controls-overlay", "standalone"], // (11)!
"orientation": "any", // (12)!
"theme_color": "#0b57d0", // (13)!
"background_color": "#ffffff", // (14)!
"color_scheme_dark": { "theme_color": "#0f172a", "background_color": "#020617" }, // (15)!
"icons": [ // (16)!
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "/icons/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" },
{ "src": "/icons/maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" },
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
{ "src": "/icons/monochrome-512.png", "sizes": "512x512", "type": "image/png", "purpose": "monochrome" }
],
"screenshots": [ // (17)!
{
"src": "/shots/board-wide.png", "sizes": "1920x1080", "type": "image/png",
"form_factor": "wide", "label": "Task board with To do, Doing and Done columns"
},
{
"src": "/shots/today-narrow.png", "sizes": "1080x2340", "type": "image/png",
"form_factor": "narrow", "label": "Today's tasks on a phone"
}
],
"categories": ["productivity", "business"], // (18)!
"shortcuts": [ // (19)!
{
"name": "New task", "short_name": "New", "description": "Create a task in your inbox",
"url": "/tasks/new?source=shortcut",
"icons": [{ "src": "/icons/shortcut-new-96.png", "sizes": "96x96", "type": "image/png" }]
},
{
"name": "Today", "url": "/tasks/today?source=shortcut",
"icons": [{ "src": "/icons/shortcut-today-96.png", "sizes": "96x96", "type": "image/png" }]
}
],
"launch_handler": { "client_mode": ["focus-existing", "auto"] }, // (20)!
"share_target": { // (21)!
"action": "/share-target",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title", "text": "text", "url": "url",
"files": [{ "name": "attachments", "accept": ["image/png", "image/jpeg", ".png", ".jpg", ".jpeg"] }]
}
},
"file_handlers": [ // (22)!
{
"action": "/open-board",
"name": "Acme task board",
"accept": { "application/vnd.acme.board+json": [".acmeboard"] },
"launch_type": "single-client"
}
],
"protocol_handlers": [ // (23)!
{ "protocol": "web+tasks", "url": "/open?link=%s" }
],
"scope_extensions": [ // (24)!
{ "type": "origin", "origin": "https://help.acme.example" }
],
"related_applications": [ // (25)!
{ "platform": "play", "id": "com.acme.tasks", "url": "https://play.google.com/store/apps/details?id=com.acme.tasks" }
],
"prefer_related_applications": false // (26)!
}
- Identity. Resolved against the origin of
start_url, so"/"meanshttps://your.origin/. It is compared exactly (/appand/app/are different apps) and should never change. If you shipped withoutid, copy the Computed App Id from DevTools (Application → Manifest) into this field instead. See App Identity & Updates. - Full name. Used in install dialogs, the app list, window titles and the Android splash screen. After installation, Chromium treats name changes as security-sensitive and asks the user to approve them.
- Short name. Used where space is limited (home-screen label, launcher, task switcher). Launchers truncate long labels, so keep it to one short word or two.
- Description. Defined in the Application Information Note, not the core spec. Chromium shows it in the richer install dialog; keep it to one or two sentences.
- Language of the default strings. A BCP 47 tag, canonicalized (
EN-usbecomesen-US). It labels the default text; it does not translate anything. - Base direction of
name,short_nameand shortcut text:ltr,rtlorauto(default). - Localized names (Chrome and Edge 148+ on desktop). Each key is a language tag; the value is a string or
{ "value", "lang"?, "dir"? }. Browsers without support fall back toname.short_name_localizedandicons_localizedwork the same way. - Launch URL. Resolved against the manifest URL; must be same-origin with the document and inside
scope. The query parameter lets analytics count installed launches. Never put a user identifier here. - Navigation scope. Pages outside it get browser UI: desktop Chromium and Chrome for Android show a toolbar with the origin and a close button, iOS opens them in Safari View Controller, and macOS Safari web apps hand them to the default browser. Prefix matching is string-based, so end it with
/. Not the same thing as the service worker scope. - Fallback display mode. One of
fullscreen,standalone,minimal-ui,browser. This is the mode used by every browser that doesn't understand the firstdisplay_overrideentry. - Custom fallback chain (Chromium 89+). The first recognized value wins; enhanced modes such as
window-controls-overlayandtabbedare only valid here. Safari and Firefox ignore this member. - Default orientation.
anykeeps tablets and foldables usable; lock only for games or camera tools. Baked into the WebAPK on Android. - Theme color. Title bar and status bar color. Any CSS color that converts to sRGB; alpha may be ignored. A
<meta name="theme-color">in an in-scope page overrides it. - Splash background. Used only while the app loads (Android splash screen). Match your CSS
bodybackground to avoid a flash. - Dark-scheme overrides. In the W3C spec but not implemented by any browser as of September 2026. Harmless to include; keep the
<meta name="theme-color" media="(prefers-color-scheme: dark)">tag until it ships. - Icons. At least one
anyicon of 144 px or more (192 and 512 recommended) for Chromium; separatemaskablefiles with the logo inside the central 80 % circle; optionalmonochromefor platforms that tint icons. Never combine"any maskable"in one entry. See Icons & Maskable Icons. - Screenshots for Chrome's richer install UI:
widefor desktop,narrow(or none) for Android; 320 to 3,840 px per side, long side at most 2.3 times the short side, at most 8 considered, and alabelfor each. On Android every narrow screenshot must share the first one's aspect ratio, andwideentries still count toward the 8, so list narrow screenshots first. See Rich Install UI. - Store categories. Lowercase strings from the known list below. Advisory only; Safari on macOS uses them to name Launchpad folders.
- Shortcuts.
nameand an in-scopeurlare required. Chromium reads at most the first 10 array entries, and Chrome for Android keeps only the first four. Put the most important first. See App Shortcuts. - Launch behavior (Chromium desktop). The first recognized
client_modewins; withfocus-existingyour page must consumewindow.launchQueue. - Share target. Files require
POSTwithmultipart/form-data. One invalid MIME type inacceptdrops the whole member in Chromium. Your service worker or server must handle thePOSTtoaction. See Web Share Target. - File handlers (Chromium desktop).
acceptmaps MIME types to extension lists that start with.; files arrive asFileSystemFileHandleobjects throughwindow.launchQueue. See File Handling. - Protocol handlers (Chromium desktop). A safelisted scheme or
web+plus ASCII letters;urlmust be in scope and contain%s. The user approves the registration on first use. - Scope extensions (Chrome and Edge 139+ desktop). Each listed origin must confirm with
/.well-known/web-app-origin-associationkeyed by this app's processedid. - Related native apps. Used by Chromium on Android for Play Store promotion (with
prefer_related_applications: true; desktop Chromium only honors it for achrome_web_storeentry, orplayon ChromeOS) and bynavigator.getInstalledRelatedApps(). Harmless when you have no native app; omit it then. - Keep
false(the default) unless you want Android users sent to the Play Store instead of installing the PWA. A JSON string"false"is ignored, not interpreted.
Sub-object shapes¶
Image resource (icons, screenshots, shortcuts[].icons)¶
| Member | Required | Type | Values and rules |
|---|---|---|---|
src | Yes | string (URL) | Resolved against the manifest URL; may be cross-origin (subject to CSP img-src) |
sizes | Strongly recommended | string | Space-separated WxH tokens ("192x192", "16x16 32x32") or "any" for scalable images; lowercase x |
type | Recommended | string (MIME type) | image/png, image/svg+xml, image/webp; lets browsers skip formats they can't use without downloading |
purpose | No | string | Space-separated any, maskable, monochrome; default any; unknown tokens are dropped, and an entry with no valid token is discarded |
label | No (screenshots: recommended) | string | Accessible name; for screenshots, the alternative text |
form_factor | Screenshots only | string | narrow or wide |
platform | Screenshots only | string | See the platform list below |
Shortcut item¶
| Member | Required | Type | Notes |
|---|---|---|---|
name | Yes | string | Non-empty; menu label |
url | Yes | string (URL) | Within scope, resolved against the manifest URL |
short_name | No | string | Used where space is limited |
description | No | string | May be exposed to assistive technology |
icons | No | array of image resources | 96 × 96 is the common size; Android uses them in the long-press menu |
name_localized, short_name_localized, description_localized, icons_localized | No | language maps | Chrome and Edge 148+ desktop |
share_target¶
| Member | Required | Values |
|---|---|---|
action | Yes | URL within scope |
method | No | GET (default) or POST |
enctype | No | application/x-www-form-urlencoded (default) or multipart/form-data (only with POST) |
params.title, params.text, params.url | No | Names of the query or form fields that receive each part |
params.files | No | Array of { "name", "accept" }; accept is a MIME type or extension, or an array of them; requires POST + multipart/form-data |
file_handlers[]¶
| Member | Required | Values |
|---|---|---|
action | Yes | URL within scope |
accept | Yes | Object: MIME type (wildcards such as image/* allowed) → array of extensions starting with . (at most 16 characters each per spec; Chromium caps the total at 300 extensions) |
name | No | File-type display name |
icons | No | File-type icons |
launch_type | No | single-client (default: one launch with all files) or multiple-clients (one launch per file) |
protocol_handlers[]¶
| Member | Required | Values |
|---|---|---|
protocol | Yes | Safelisted scheme (bitcoin, ftp, ftps, geo, im, irc, ircs, magnet, mailto, matrix, mms, news, nntp, openpgp4fpr, sftp, sip, sms, smsto, ssh, tel, urn, webcal, wtai, xmpp) or web+ followed by one or more ASCII lowercase letters; no trailing colon. Chromium (since 86) also accepts the non-standard decentralized-web schemes cabal, dat, did, doi, dweb, ethereum, hyper, ipfs, ipns and ssb |
url | Yes | Same-origin https URL within scope, containing %s (replaced with the percent-encoded URL being handled) |
launch_handler, scope_extensions, related_applications, migrate_from¶
| Object | Shape | Notes |
|---|---|---|
launch_handler | { "client_mode": "<mode>" } or { "client_mode": ["<mode>", "<fallback>"] } | Chromium reads only client_mode; fields such as route_to from the old origin trial are ignored |
scope_extensions[] | { "type": "origin", "origin": "https://other.example" } | https origins only; Chromium accepts at most 10; each origin confirms with a .well-known/web-app-origin-association file |
related_applications[] | { "platform", "url"?, "id"?, "min_version"?, "fingerprints"? } | platform plus at least one of url or id; Chromium acts on play, windows, webapp and chrome_web_store |
migrate_from[] | "<old id>" or { "id", "install_url"?, "behavior"? } | behavior: suggest (default) or force; ids resolve against the manifest URL and must be same-site; requires an explicit top-level id |
migrate_to | { "id", "install_url"? } | Optional signal in the old app's manifest |
tab_strip | { "home_tab": { "scope_patterns": [...] }, "new_tab_button": { "url": "..." } } | Only with "display_override": ["tabbed"] |
note_taking | { "new_note_url": "/notes/new" } | Within scope; ChromeOS only |
edge_side_panel | { "preferred_width": 480 } | Microsoft Edge; being deprecated |
Value lists¶
display and display_override values¶
| Value | Valid in display | Valid in display_override | What the user sees | Notes |
|---|---|---|---|---|
fullscreen | ✅ | ✅ | No browser UI, no status bar where the OS allows it | Safari opens it as standalone |
standalone | ✅ | ✅ | Own window, no URL bar; OS status bar and title bar remain | The default choice |
minimal-ui | ✅ | ✅ | Own window with minimal navigation controls (back, reload) | Falls back to browser where unsupported, including Safari |
browser | ✅ | ✅ | A normal browser tab | Not installable in Chromium; the spec default |
window-controls-overlay | ❌ (ignored) | ✅ | App content extends into the title bar area on desktop | Chromium desktop; see Window Controls Overlay |
tabbed | ❌ (ignored) | ✅ | App window with a tab strip | ChromeOS; behind flags elsewhere |
unframed | ❌ | ✅ (IWA only) | Window without a title bar | Isolated Web Apps only; formerly borderless in Chromium |
The standard fallback chain is fullscreen → standalone → minimal-ui → browser: when a browser can't provide the requested mode, it tries the next one. display_override replaces that chain with your own list, and the browser then falls back to display. The matching CSS media feature is @media (display-mode: <value>), which also has a picture-in-picture value that is not a manifest value.
orientation values¶
| Value | Meaning |
|---|---|
any | Rotates freely |
natural | The device's natural orientation (portrait on most phones, landscape on many tablets) |
portrait | Either portrait variant, following rotation |
portrait-primary | Upright portrait only |
portrait-secondary | Upside-down portrait only |
landscape | Either landscape variant |
landscape-primary | Primary landscape only |
landscape-secondary | Secondary (rotated 180°) landscape only |
These are the Screen Orientation API's lock types. screen.orientation.lock() can change the orientation at runtime where locking is allowed, and unlock() returns to the manifest value. WCAG 2.2 success criterion 1.3.4 (Orientation, level AA) forbids locking unless a specific orientation is essential.
Icon purpose values¶
| Value | Meaning | Design rule |
|---|---|---|
any (default) | Use the icon as-is in any context | Transparent background allowed; logo can fill the canvas |
maskable | The OS may crop the icon to its own shape | Opaque, full-bleed background; all important content inside a centered circle of 40 % radius (the central 80 %) |
monochrome | The OS uses only the alpha channel and fills it with one color | Single-color glyph on transparency; color information is discarded |
Combining tokens ("any maskable") is valid syntax, but it makes the same padded image appear small wherever it's used unmasked. Ship separate files.
categories known values¶
The W3C Application Information Note lists these known categories; the member accepts any string, but stores recognize these, and lowercase is recommended:
beauty, books, books & reference, business, cars, dating, design, developer, developer tools, development, education, entertainment, events, fashion, finance, fitness, food, fundraising, games, government, graphics, graphics & design, health, health & fitness, kids, lifestyle, magazines, medical, multimedia, multimedia design, music, navigation, network, networking, news, parenting, personalization, pets, photo, photo & video, politics, productivity, reference, security, shopping, social, social networking, sports, transportation, travel, utilities, video, weather.
screenshots form_factor and platform values¶
| Field | Values |
|---|---|
form_factor | narrow (phones; used by Chrome for Android), wide (desktop; the only value desktop Chrome uses) |
platform (operating systems) | android, chromeos, ios, ipados, kaios, macos, windows, xbox |
platform (distribution) | chrome_web_store, play, itunes, microsoft-inbox, microsoft-store |
Chromium does not read platform. Use it only for screenshots that show platform-specific features.
launch_handler.client_mode values¶
| Value | Behavior when the app is already open |
|---|---|
auto (default) | The browser decides: desktop Chromium opens a new window; mobile reuses the existing one |
navigate-new | Opens a new app window at the launch URL |
navigate-existing | Focuses the most recently used app window and navigates it (unsaved state is lost) |
focus-existing | Focuses the most recently used app window without navigating and enqueues LaunchParams in window.launchQueue |
share_target methods and encodings¶
method | enctype | Data arrives as | Files |
|---|---|---|---|
GET | application/x-www-form-urlencoded (only valid value) | Query string on action (spaces as +; decode with URLSearchParams) | ❌ |
POST | application/x-www-form-urlencoded | Form body | ❌ |
POST | multipart/form-data | Multipart body; read with request.formData() in the service worker | ✅ |
dir values¶
ltr, rtl, auto (default). Write every enumerated manifest value in lowercase exactly as listed on this page; don't rely on any engine normalizing case.
URL resolution rules¶
| Member | Resolved against | Constraint | If it fails |
|---|---|---|---|
start_url | Manifest URL | Same origin as the document | Falls back to the document URL (and desktop Chromium reports start-url-not-valid) |
id | Origin of start_url | Same origin as start_url | Falls back to start_url |
scope | Manifest URL | Same origin as the document; start_url must be inside it | Falls back to the directory of start_url |
icons[].src, screenshots[].src | Manifest URL | None (cross-origin allowed; CSP applies) | Entry dropped |
shortcuts[].url | Manifest URL | Within scope | Shortcut dropped |
share_target.action | Manifest URL | Within scope | Whole member dropped |
file_handlers[].action | Manifest URL | Within scope | Handler dropped |
protocol_handlers[].url | Manifest URL | Same origin, within scope, contains %s | Handler dropped |
note_taking.new_note_url | Manifest URL | Within scope | Ignored |
related_applications[].url | Manifest URL | None | Entry needs url or id |
migrate_from[].id | Manifest URL | Same site as the document | Entry dropped |
"Within scope" is a string-prefix test on the processed URLs. "scope": "/app" also matches /apple; end scopes with /. Details: App Identity & Updates.
Legacy and iOS meta tags: equivalence table¶
Before manifests, every vendor invented <meta> and <link> tags. Some still matter, some are fallbacks, some are dead weight:
| Tag | Manifest equivalent | Read by (September 2026) | Keep? |
|---|---|---|---|
<meta name="theme-color" content="…" media="…"> | theme_color (and future color_scheme_dark) | Chromium (in installed apps on desktop), Safari 15+ (installed web apps only since Safari 26), Samsung Internet | Keep. Per-page override within scope, and the only working light/dark theme color |
<link rel="apple-touch-icon" href="…"> | icons | Safari on iOS, iPadOS and macOS; Chromium's fallback metadata | Keep a 180 × 180 opaque PNG; Safari ignores manifest icons when it is present |
<link rel="apple-touch-icon-precomposed"> | icons | Legacy iOS (before iOS 7 applied gloss effects) | Remove; plain apple-touch-icon is enough |
<meta name="apple-mobile-web-app-capable" content="yes"> | "display": "standalone" | Safari before iOS 26 (made a Home Screen icon open standalone); Chromium's fallback metadata when the unprefixed tag is absent | Legacy. iOS 26+ opens every Home Screen site as a web app unless the user opts out |
<meta name="mobile-web-app-capable" content="yes"> | "display": "standalone" | Chromium's fallback metadata for sites without a complete manifest | Legacy; use the manifest |
<meta name="apple-mobile-web-app-title" content="…"> | short_name | Safari (Home Screen label default) | Optional; keep it identical to short_name |
<meta name="apple-mobile-web-app-status-bar-style" content="…"> | None | Safari on iOS and iPadOS, in web app mode | Optional: default, black or black-translucent (content extends under the status bar; pair with viewport-fit=cover and env(safe-area-inset-*)) |
<link rel="apple-touch-startup-image" href="…" media="…"> | background_color + icons + name (Android builds its splash from these) | Safari on iOS and iPadOS | Optional; one image per device size and orientation, selected by media queries. Safari doesn't generate a splash screen from the manifest |
<meta name="application-name" content="…"> | name | Chromium's fallback metadata | Optional fallback |
<link rel="icon" href="…"> | icons | All browsers (tabs, bookmarks); Chromium's fallback install icon | Keep for tabs |
<link rel="mask-icon" href="…" color="…"> | Closest: icons with purpose: "monochrome" | Legacy Safari on macOS (monochrome pinned-tab icon; Safari 12 and later show regular favicons in tabs) | Optional legacy; unrelated to installation |
<meta name="msapplication-TileColor">, msapplication-TileImage, msapplication-config, msapplication-starturl, msapplication-navbutton-color | theme_color, icons, start_url | Internet Explorer and EdgeHTML-based Edge, both retired | Remove |
<meta name="viewport" content="…, viewport-fit=cover"> | None | All mobile browsers | Keep when you use black-translucent or fullscreen, so env(safe-area-inset-*) is non-zero |
A head that covers every engine without redundancy:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<link rel="manifest" href="/app.webmanifest">
<!-- Light and dark theme colors: the manifest's color_scheme_dark isn't implemented yet. -->
<meta name="theme-color" content="#0b57d0" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0f172a" media="(prefers-color-scheme: dark)">
<!-- Tab icons. -->
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/icons/icon.svg" type="image/svg+xml">
<!-- Safari uses this instead of manifest icons: 180x180, opaque, square corners. -->
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
<!-- Optional Safari refinements for Home Screen web apps. -->
<meta name="apple-mobile-web-app-title" content="Tasks">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
Splash screens per device and status-bar handling are covered in Splash Screens & Theming and iOS & iPadOS.
Size and count limits¶
| Item | Limit | Source of the limit |
|---|---|---|
| Primary install icon (Chromium) | At least 144 × 144; at most 1024 × 1024 on desktop | Chromium installability check |
| Maskable safe zone | Centered circle with a radius of 40 % of the icon size | Web App Manifest spec |
| Shortcuts read | First 10 array entries (Chromium) | Chromium manifest parser |
| Shortcuts used on Android | First four processed items (kMaxShortcuts = 4) | Chromium on Android |
| Screenshots considered | At most 8. On Android the count includes wide entries, so list narrow ones first; DevTools warns above 5 for mobile | Chromium richer install UI |
| Screenshot dimensions | 320 to 3,840 px per side; long side at most 2.3 × the short side | Chromium richer install UI |
scope_extensions entries | 10, each origin string at most 2,000 characters | Chromium manifest parser |
file_handlers extensions | 300 in total; each extension at most 16 characters (spec) | Chromium parser; manifest incubations spec |
What changes after installation¶
Manifest edits reach installed apps through the browser's update process, not instantly:
| Change | Desktop Chromium 144+ | Chrome for Android (WebAPK) | Safari |
|---|---|---|---|
name, short_name | Pending until the user approves it (Review app update in the app menu) | Confirmation dialog, then a new WebAPK is minted. Checks run only when the WebAPK is launched and at least a day has passed since the last check; the new APK installs in the background, often hours later | Name fixed at install time (the user can edit it while adding) |
icons | Only if the icons entries change (new URLs); under 10 % image difference applies silently (throttled to once a day), otherwise user review | Below an 11 % image difference (Chromium's WEB_APK_ICON_UPDATE_BLOCKED_AT_PERCENTAGE, an average per-pixel color difference): applied with the next WebAPK. Larger changes need an icon confirmation dialog that current Chromium keeps disabled by default, so they are not applied | Fixed at install time |
start_url, scope, display, colors, shortcuts, handlers | Applied silently on the next page load that links the manifest | New WebAPK for the members baked into it | Fixed at install time on iOS |
id | Can't change: a new id is a new app | Same | Same |
Replacing an icon file in place without changing its URL never reaches desktop Chromium users. Publish new icons under new URLs. The full model is in App Identity & Updates.
Common validation mistakes¶
| Mistake | What the browser does | Fix |
|---|---|---|
"display": "window-controls-overlay" | Ignores it; browser applies and Chromium won't install | "display": "standalone", "display_override": ["window-controls-overlay"] |
"prefer_related_applications": "false" | Ignores the string; default false applies (by luck) | Use JSON booleans |
"categories": "productivity" | Ignored (not an array) | ["productivity"] |
"sizes": "192X192" or missing sizes | Icon can't satisfy the size check | Lowercase x, always declare sizes |
Only maskable icons | Chromium: manifest-missing-suitable-icon | Add any icons |
"purpose": "any maskable" | Valid, but the padded icon looks shrunken in any contexts | Separate files |
start_url outside scope | scope is discarded; the default scope applies | Put start_url inside scope |
| Relative URLs in a manifest on a CDN | Resolve against the CDN origin and fail the same-origin checks | Serve the manifest from the app origin, or use absolute URLs |
| Shortcut or handler URL outside scope | Entry dropped with a console warning only | Keep every navigational URL inside scope |
Changing start_url without an id | Existing installs keep the old app; new installs get a second one | Set id to the old computed identity first |
| Trailing comma or comment in the JSON | Whole manifest discarded | Validate JSON in CI |
Cache-Control: max-age=31536000 on the manifest | Updates take up to a year to be seen | Revalidate the manifest |
Debugging a manifest quickly¶
- Chrome and Edge: DevTools → Application → Manifest shows every processed member, the computed app ID, parser warnings (prefixed
Manifest:in the console) and installability errors. The Identity section's Computed App Id is the value to copy intoid. - Safari: connect Web Inspector to the device; Safari has no manifest pane, so check the Home Screen result directly. Remember that
apple-touch-iconoverrides manifest icons. - Firefox: DevTools → Application → Manifest displays the parsed members and icons, even though desktop Firefox doesn't install from manifests.
- CI: use the Chrome DevTools Protocol's
Page.getAppManifestandPage.getInstallabilityErrors, as shown in Installability Criteria, or the tools in Browser DevTools and Automated Testing.
Further reading¶
On this site
- Members Reference: processing rules, error messages and gotchas for every member
- Web App Manifest: serving, linking and generating manifests
- Icons & Maskable Icons
- Display Modes
- App Identity & Updates
- Advanced & Integration Members
- Installability Criteria
- Production Checklist
External references