# Measure PWA display, install, and offline events

> Record the active display mode, successful appinstalled events, and online or offline transitions without treating them as installation proof.

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.

## Browser and platform support

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.

## Choose the event shape

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.

:::caution
[Create a standalone app](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/How_to/Create_a_standalone_app)
states that there is no reliable cross-browser way for a PWA to determine whether it is
installed. Treat `display-mode` as the active presentation mode and `appinstalled` as an event,
not as a durable installation flag.
:::

## Examples

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

### Record display mode at page load

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

```js
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

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.

```js
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.

## Interpret the signals

- `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.

## See also

- [Make a PWA installable](/guides/installable/)
- [Manifest display browser support](/compatibility/manifest-display/)
- [Offline strategies](/guides/offline/)
- [display-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/%40media/display-mode) (MDN)
- [Window: appinstalled event](https://developer.mozilla.org/en-US/docs/Web/API/Window/appinstalled_event) (MDN)
- [Navigator: onLine property](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine) (MDN)