Skip to main content
CodeRocket
HTMLlowsetup

Meet PWA installability criteria

rule · pwa-installability

A Progressive Web App (PWA) becomes installable when the browser detects it meets a minimum set of criteria. Once installable, the browser shows an in-page install prompt (or address-bar button), and the user can add the app to their home screen or taskbar.

Code Example

JSON
// public/manifest.json
{
  "name": "Acme Dashboard",
  "short_name": "Acme",
  "description": "Track your metrics at a glance.",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#3b82f6",
  "orientation": "any",
  "lang": "en",
  "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/icon-maskable-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ],
  "screenshots": [
    {
      "src": "/screenshots/desktop.png",
      "sizes": "1280x720",
      "type": "image/png",
      "form_factor": "wide",
      "label": "Dashboard on desktop"
    },
    {
      "src": "/screenshots/mobile.png",
      "sizes": "390x844",
      "type": "image/png",
      "form_factor": "narrow",
      "label": "Dashboard on mobile"
    }
  ]
}

Why It Matters

Installable PWAs appear in the browser's address bar install prompt and in app stores, giving users a native-app experience without an app store listing. Studies consistently show that installed PWAs have higher engagement and retention than browser-only equivalents — users who install spend 3× more time in the app on average.

Minimum Installability Requirements

RequirementWhat is needed
HTTPSSite must be served over a secure origin (or localhost for dev)
Web App ManifestA valid manifest.json linked from <head>
Service WorkerA registered SW with a fetch handler
IconsAt least a 192×192 and a 512×512 PNG icon

Chromium-based browsers also require the manifest to have name (or short_name), start_url, display, and at least one valid icon before showing the install prompt.

Linking the Manifest

HTML
<!-- index.html or layout.tsx -->
<head>
  <!-- Manifest -->
  <link rel="manifest" href="/manifest.json" />
 
  <!-- iOS meta tags (Safari does not read the manifest for these) -->
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="default" />
  <meta name="apple-mobile-web-app-title" content="Acme" />
  <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
 
  <!-- Theme colour -->
  <meta name="theme-color" content="#3b82f6" />
</head>

Display Modes

ModeBehaviour
standaloneApp opens in its own window without browser UI — most app-like
minimal-uiLike standalone but with minimal browser navigation controls
fullscreenNo browser UI; suitable for games and immersive experiences
browserOpens in a regular browser tab (not considered "installed")

Maskable Icons

Android applies a platform shape (circle, squircle, etc.) to home-screen icons. Without a maskable icon the platform simply scales down the regular icon, which often produces awkward whitespace. A maskable icon fills the entire canvas; Android clips it to the shape.

The safe zone is a circle with radius = 40 % of the icon's smallest dimension. Keep the main logo or graphic within this zone so it is never clipped.

Text
512×512 icon — safe zone = circle of radius 204 px centred at (256, 256)

Use Maskable.app (opens in a new tab) to preview how your icon looks under each Android shape.

Next.js Manifest

TypeScript
// app/manifest.ts  (Next.js 13+ App Router)
import type { MetadataRoute } from 'next'
 
export default function manifest(): MetadataRoute.Manifest {
  return {
    name: 'Acme Dashboard',
    short_name: 'Acme',
    description: 'Track your metrics at a glance.',
    start_url: '/',
    display: 'standalone',
    background_color: '#ffffff',
    theme_color: '#3b82f6',
    icons: [
      { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
      { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
      {
        src: '/icons/icon-maskable-512.png',
        sizes: '512x512',
        type: 'image/png',
        purpose: 'maskable',
      },
    ],
  }
}

Prompting the Install

The browser fires the beforeinstallprompt event when the app is installable. Capture and show it at the right time rather than immediately:

TypeScript
let deferredPrompt: BeforeInstallPromptEvent | null = null
 
window.addEventListener('beforeinstallprompt', (event) => {
  // Prevent the automatic mini-infobar on mobile
  event.preventDefault()
  deferredPrompt = event as BeforeInstallPromptEvent
  showInstallButton()
})
 
async function triggerInstallPrompt() {
  if (!deferredPrompt) return
 
  deferredPrompt.prompt()
  const { outcome } = await deferredPrompt.userChoice
  console.info('Install prompt outcome:', outcome)  // 'accepted' | 'dismissed'
  deferredPrompt = null
  hideInstallButton()
}
 
window.addEventListener('appinstalled', () => {
  console.info('PWA installed')
  hideInstallButton()
})

Support Notes

  • Installability prompts and app-install UX vary by browser and platform, so verify the supported install flow on the target browsers instead of assuming one Chromium path covers all users.
  • Document the graceful fallback when a browser supports the manifest but not the full install prompt or standalone experience.

Verification

Automated Checks

  • Open DevTools → ApplicationManifest and confirm all required fields are green with no warnings.
  • Run a Lighthouse PWA audit and check that "Installable" criteria all pass.
  • Use PWABuilder (opens in a new tab) to get a detailed installability report and generate store-ready packages.

Manual Checks

  • On an Android device or Chrome, look for the install button in the address bar after visiting the site.