# Debug a PWA

> How to use the Chrome DevTools Application panel (Manifest, Service workers, Cache storage) to inspect and troubleshoot a PWA while developing it.

By the end of this guide you can read a PWA's parsed manifest, name the exact
installability failure Chrome sees, force a service worker update, simulate offline, and
inspect or wipe the Cache Storage entries, all from the Chrome DevTools **Application**
panel. The panel is Chrome's own tooling; Firefox and Safari expose different
developer-tools surfaces that this guide does not cover.

You need Chrome with DevTools open on the page under test. Open the panel with
Command+Shift+P (macOS) or Control+Shift+P (Windows, Linux, ChromeOS), type
`application`, and choose **Show Application**; the sidebar lists **Manifest**,
**Service workers**, and **Storage** under "Application", and **Cache storage** under
"Storage".

## Read the parsed manifest and the Installability verdict

Select **Manifest**. The pane shows the manifest URL under **App Manifest**, then the
**Identity**, **Presentation**, and **Icons** sections with every declared icon rendered.
The checkbox **Show only the minimum safe area for maskable icons** crops each icon to the
area a masked launcher keeps, which is the quickest way to catch artwork that touches the
edge. Under **Protocol Handlers**, the **Test protocol** button opens a URL with a declared
scheme so you can confirm the registration. An **Installability** section appears only when
Chrome finds a problem, and each line is one of a fixed set of strings from the DevTools
front end. The ones that come up most while developing are:

- `Page has no manifest <link> URL`
- `Manifest couldn't be fetched, is empty, or couldn't be parsed`
- `Manifest doesn't contain a 'name' or 'short_name' field`
- `Manifest 'start_url' isn't valid`
- `Manifest 'display' property must be one of 'standalone', 'fullscreen', or 'minimal-ui'`
- `Couldn't download a required icon from the manifest`
- `Page isn't served from a secure origin`

Each names the manifest member or server condition to fix; the section disappears once the
page meets the criteria.

## Control the service worker from the Service workers pane

Select **Service workers**. For the registration in scope the pane prints **Source** (the
script URL and when it was received), **Status** (`#N activated and is running`, with
**stop** and **start** links), and **Clients**. Three checkboxes change how Chrome treats
the worker while the box is ticked:

| Control | Effect |
|---|---|
| **Offline** | Puts the page into the same offline mode as the Network panel |
| **Update on reload** | Forces the service worker to update on every page load |
| **Bypass for network** | Sends every request straight to the network, ignoring the fetch handler |

The **Update** link runs one update check; **Unregister** removes the registration; **Push**
and **Sync** dispatch a `push` event (default payload `Test push message from DevTools`) and a
`sync` event to the worker. The **Update Cycle** table lists the install, wait, and activate
timestamps of each version, which tells you which phase a worker that "will not update" is
stuck in. The page script should still guard registration so a browser without the API loads
the page normally:

```js
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then((registration) => {
    console.log('scope', registration.scope);
  });
} else {
  console.info('Service workers unavailable; running without offline support');
}
```

A worker with `waiting` set in the table is installed but not controlling the page because
an older version still owns an open client; close every tab of the origin or tick **Update
on reload** to let it activate.

## Inspect and clear Cache storage

Under **Storage**, expand **Cache storage** to see one entry per `caches.open()` name. Select
one to list the cached requests with their response status, content type, and size; an entry
marked opaque is a cross-origin response fetched without CORS; Chrome's guide notes that opaque
entries affect how their size counts against the origin's quota. **Clear storage** (top of
the Application section) unregisters service workers and deletes caches, IndexedDB, and local
storage in one click, which resets the origin between test runs.

:::observed
Chrome's DevTools guide for PWAs (developer.chrome.com, read 2026-10-03) states: "Note that
the first time you open a cache and add a resource to it, DevTools might not detect the
change", followed by "Reload the page and you should see the cache". A `cache.addAll()` that
appears to have done nothing in the **Cache storage** tree is therefore not evidence of a bug
until the page has been reloaded once with the tree open.
:::

## Reproduce an offline launch

Tick **Offline** in the Service workers pane, then reload. A page with a working fetch handler
renders from cache; a page without one shows Chrome's offline error page. Since Chrome 108 on
Android and Chrome 112 on desktop a site installs from the browser menu without a fetch
handler, and Chrome supplies a default offline page for installed apps that lack one, so an
offline failure in this test no longer blocks installation but still means your users see
Chrome's page rather than yours. Untick **Offline** before testing anything else; the setting
persists for the DevTools session.

## See also

- [Debugging service workers](/reference/service-worker/debugging/)
- [The update flow and skipWaiting](/reference/service-worker/update-skipwaiting/)
- [Cache Storage](/reference/storage/cache-storage/)
- [Debug Progressive Web Apps](https://developer.chrome.com/docs/devtools/progressive-web-apps/) (developer.chrome.com)
- [Application panel overview](https://developer.chrome.com/docs/devtools/application/) (developer.chrome.com)