Progressive Web Apps (PWA)
Web App Manifest
Understanding the Web App Manifest
The Web App Manifest is a JSON file that tells the browser how your PWA should behave when installed on the user's device. It controls the app's appearance, behavior, and provides metadata that makes your web app feel like a native application.
Creating manifest.json
Create a file named manifest.json in your project root and link it in your HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My PWA</title>
<link rel="manifest" href="/manifest.json">
</head>
<body>
<h1>Progressive Web App</h1>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My PWA</title>
<link rel="manifest" href="/manifest.json">
</head>
<body>
<h1>Progressive Web App</h1>
</body>
</html>
Essential Manifest Properties
Here's a complete example of a manifest.json file with all essential properties:
{
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "A powerful progressive web application",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196F3",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
"name": "My Progressive Web App",
"short_name": "MyPWA",
"description": "A powerful progressive web application",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196F3",
"orientation": "portrait-primary",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
Property Breakdown
name vs short_name:
name: Full application name shown during installation and in app launchers (max 45 characters recommended).
short_name: Abbreviated name shown on home screen under the icon (max 12 characters recommended).
name: Full application name shown during installation and in app launchers (max 45 characters recommended).
short_name: Abbreviated name shown on home screen under the icon (max 12 characters recommended).
1. Display Modes
The display property controls how your app appears:
/* standalone - Most common for PWAs */
"display": "standalone"
// Looks like native app, no browser UI
/* fullscreen - Immersive experience */
"display": "fullscreen"
// Full screen, hides all browser UI and status bar
/* minimal-ui - Some browser controls */
"display": "minimal-ui"
// Minimal browser UI (back/forward/reload)
/* browser - Default browser experience */
"display": "browser"
// Opens in regular browser tab
"display": "standalone"
// Looks like native app, no browser UI
/* fullscreen - Immersive experience */
"display": "fullscreen"
// Full screen, hides all browser UI and status bar
/* minimal-ui - Some browser controls */
"display": "minimal-ui"
// Minimal browser UI (back/forward/reload)
/* browser - Default browser experience */
"display": "browser"
// Opens in regular browser tab
Best Practice: Use
"standalone" for most PWAs. It provides the best balance between native feel and user familiarity.2. Colors
Colors enhance the app experience and branding:
{
"theme_color": "#2196F3",
// Affects browser toolbar, status bar
// Shows in task switcher on Android
"background_color": "#ffffff"
// Splash screen background while app loads
// Should match your app's actual background
}
"theme_color": "#2196F3",
// Affects browser toolbar, status bar
// Shows in task switcher on Android
"background_color": "#ffffff"
// Splash screen background while app loads
// Should match your app's actual background
}
3. Start URL
Controls where the app opens when launched:
{
"start_url": "/"
// Home page
"start_url": "/dashboard"
// Specific page
"start_url": "/?source=pwa"
// With tracking parameter
}
"start_url": "/"
// Home page
"start_url": "/dashboard"
// Specific page
"start_url": "/?source=pwa"
// With tracking parameter
}
4. Orientation
Lock screen orientation for better UX:
{
"orientation": "portrait-primary"
// portrait-primary, portrait-secondary
// landscape-primary, landscape-secondary
// portrait, landscape, any (default)
}
"orientation": "portrait-primary"
// portrait-primary, portrait-secondary
// landscape-primary, landscape-secondary
// portrait, landscape, any (default)
}
Icon Requirements
Icons are crucial for the installed app appearance. You need multiple sizes:
Critical Icon Sizes:
• 192x192: Required - Chrome/Android home screen
• 512x512: Required - Splash screens and app stores
• 72x72 to 384x384: Recommended for various devices
Use PNG format with transparent backgrounds. The
•
•
•
• 192x192: Required - Chrome/Android home screen
• 512x512: Required - Splash screens and app stores
• 72x72 to 384x384: Recommended for various devices
Use PNG format with transparent backgrounds. The
purpose field supports:•
"any" - Standard icon•
"maskable" - Adaptive icon for Android (safe zone in center)•
"maskable any" - Both purposesAdvanced Manifest Properties
{
"scope": "/",
// Defines navigation scope for the PWA
"categories": ["productivity", "utilities"],
// App categories for app stores
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow"
}
],
// Used in app stores and installation prompts
"shortcuts": [
{
"name": "New Message",
"short_name": "New",
"description": "Start a new message",
"url": "/messages/new",
"icons": [{ "src": "/icons/new.png", "sizes": "192x192" }]
},
{
"name": "Inbox",
"short_name": "Inbox",
"description": "View your inbox",
"url": "/messages/inbox",
"icons": [{ "src": "/icons/inbox.png", "sizes": "192x192" }]
}
],
// App shortcuts (right-click menu on desktop)
"lang": "en-US",
// Primary language
"dir": "ltr",
// Text direction: ltr, rtl, auto
"prefer_related_applications": false,
// Prefer native app over PWA
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=com.example.app",
"id": "com.example.app"
}
]
}
"scope": "/",
// Defines navigation scope for the PWA
"categories": ["productivity", "utilities"],
// App categories for app stores
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "750x1334",
"type": "image/png",
"form_factor": "narrow"
}
],
// Used in app stores and installation prompts
"shortcuts": [
{
"name": "New Message",
"short_name": "New",
"description": "Start a new message",
"url": "/messages/new",
"icons": [{ "src": "/icons/new.png", "sizes": "192x192" }]
},
{
"name": "Inbox",
"short_name": "Inbox",
"description": "View your inbox",
"url": "/messages/inbox",
"icons": [{ "src": "/icons/inbox.png", "sizes": "192x192" }]
}
],
// App shortcuts (right-click menu on desktop)
"lang": "en-US",
// Primary language
"dir": "ltr",
// Text direction: ltr, rtl, auto
"prefer_related_applications": false,
// Prefer native app over PWA
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=com.example.app",
"id": "com.example.app"
}
]
}
Validating Your Manifest
Testing Checklist:
1. Chrome DevTools:
• Open DevTools > Application > Manifest
• Check all properties display correctly
• Verify icons load at all sizes
2. Lighthouse Audit:
• Run Lighthouse PWA audit
• Should pass "Installable" checks
3. Browser Compatibility:
• Test manifest in Chrome, Edge, Safari
• Verify installation prompts appear
4. Icon Quality:
• Use high-resolution source images
• Ensure maskable icons have safe zone
• Test on actual devices
1. Chrome DevTools:
• Open DevTools > Application > Manifest
• Check all properties display correctly
• Verify icons load at all sizes
2. Lighthouse Audit:
• Run Lighthouse PWA audit
• Should pass "Installable" checks
3. Browser Compatibility:
• Test manifest in Chrome, Edge, Safari
• Verify installation prompts appear
4. Icon Quality:
• Use high-resolution source images
• Ensure maskable icons have safe zone
• Test on actual devices
Common Manifest Mistakes
Avoid These Errors:
❌ Missing required icon sizes (192x192, 512x512)
❌ Incorrect MIME type in <link> tag (use application/manifest+json)
❌ Invalid JSON syntax (trailing commas, unquoted keys)
❌ Icons with wrong aspect ratio or low resolution
❌ start_url not matching actual app structure
❌ theme_color not matching app design
❌ Forgetting to serve manifest with HTTPS
❌ Missing required icon sizes (192x192, 512x512)
❌ Incorrect MIME type in <link> tag (use application/manifest+json)
❌ Invalid JSON syntax (trailing commas, unquoted keys)
❌ Icons with wrong aspect ratio or low resolution
❌ start_url not matching actual app structure
❌ theme_color not matching app design
❌ Forgetting to serve manifest with HTTPS
Practical Example: Complete Setup
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Task Manager PWA</title>
<!-- Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- Theme color for browser UI -->
<meta name="theme-color" content="#2196F3">
<!-- Apple-specific meta tags -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="TaskMgr">
<link rel="apple-touch-icon" href="/icons/icon-192x192.png">
</head>
<body>
<h1>Task Manager</h1>
<button id="installBtn" style="display: none;">Install App</button>
<script>
// Installation prompt handling
let deferredPrompt;
const installBtn = document.getElementById('installBtn');
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
installBtn.style.display = 'block';
});
installBtn.addEventListener('click', async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`User response: ${outcome}`);
deferredPrompt = null;
installBtn.style.display = 'none';
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Task Manager PWA</title>
<!-- Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- Theme color for browser UI -->
<meta name="theme-color" content="#2196F3">
<!-- Apple-specific meta tags -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="TaskMgr">
<link rel="apple-touch-icon" href="/icons/icon-192x192.png">
</head>
<body>
<h1>Task Manager</h1>
<button id="installBtn" style="display: none;">Install App</button>
<script>
// Installation prompt handling
let deferredPrompt;
const installBtn = document.getElementById('installBtn');
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
installBtn.style.display = 'block';
});
installBtn.addEventListener('click', async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`User response: ${outcome}`);
deferredPrompt = null;
installBtn.style.display = 'none';
});
</script>
</body>
</html>
Next Steps: In the next lesson, we'll dive deep into Service Workers - the engine that powers offline functionality and makes your PWA truly powerful.