Skip to content

Measure PWA display, install, and offline events

Published

PWA analytics can record the active display mode and a successful appinstalled event received by the current page. It can also record browser-reported online or offline transitions. Keep these signals separate; none reliably proves installation across browsers.

The CSS display-mode media feature can distinguish modes such as browser and standalone as well as minimal-ui and fullscreen. The active mode is not the same as installation status: MDN notes that an installed PWA can open in a browser tab and that an ordinary page can enter fullscreen.

MDN marks appinstalled as limited availability. It fires when a browser successfully installs a page as an application, but code that never receives it cannot conclude that the app is not installed. The online and offline events follow changes to navigator.onLine; that value is a network hint, not a reachability check for the analytics endpoint.

Keep the collected fields tied to the documented signal. The example sends an event name with an ISO timestamp and a small detail object. It does not attach an account identifier or URL query. Form values are also excluded. Add collection and retention rules that match your product’s privacy policy before deploying an analytics endpoint.

Records stay in a local backlog until the application submits them.

Test the documented modes in order and record browser as the fallback. matchMedia is also used to listen for a later mode change.

const displayQueries = [
['window-controls-overlay', '(display-mode: window-controls-overlay)'],
['fullscreen', '(display-mode: fullscreen)'],
['standalone', '(display-mode: standalone)'],
['minimal-ui', '(display-mode: minimal-ui)'],
];
function activeDisplayMode() {
if (!('matchMedia' in window)) return 'browser';
return displayQueries.find(([, query]) => matchMedia(query).matches)?.[0] ?? 'browser';
}
function record(name, detail = {}) {
const event = { name, detail, occurredAt: new Date().toISOString() };
const backlog = JSON.parse(localStorage.getItem('pwa-analytics') ?? '[]');
backlog.push(event);
localStorage.setItem('pwa-analytics', JSON.stringify(backlog));
}
record('pwa_display_mode', { mode: activeDisplayMode() });
if ('matchMedia' in window) {
for (const [, query] of displayQueries) {
matchMedia(query).addEventListener('change', () => {
record('pwa_display_mode', { mode: activeDisplayMode() });
});
}
}

The storage in this example is a local backlog. Replace record() with the ingestion and retry policy used by your application.

Record installation and connectivity events

Section titled “Record installation and connectivity events”

Feature-detect appinstalled before adding its listener. The fallback records that this page cannot observe the event; it does not record an installation failure. The online state is recorded as the browser reports it.

if ('onappinstalled' in window) {
window.addEventListener('appinstalled', () => {
record('pwa_appinstalled');
});
} else {
record('pwa_appinstalled_unavailable');
}
if ('ononline' in window && 'onoffline' in window) {
window.addEventListener('online', () => {
record('pwa_connectivity', { state: 'online' });
});
window.addEventListener('offline', () => {
record('pwa_connectivity', { state: 'offline' });
});
record('pwa_connectivity', {
state: navigator.onLine ? 'online' : 'offline',
});
} else {
record('pwa_connectivity_unavailable');
}

An offline record should be queued locally because the analytics server may not be reachable. A later application request can submit the backlog and remove only the records the server accepts.

  • pwa_display_mode describes the top-level browsing context at the time it was sampled.
  • pwa_appinstalled means this page received the documented successful-install event.
  • pwa_appinstalled_unavailable means this page could not attach that event listener.
  • pwa_connectivity reflects navigator.onLine and its events, not a successful request to the application or analytics server.

Keep those meanings separate in reports. For example, do not rename a standalone sample to “installed user”, and do not treat absence of pwa_appinstalled as an uninstall.