# How Service Worker Registration Works for PWAs in Create React App

> Discover how service worker registration works in Create React App. Learn to easily opt-in and manage your PWA's offline capabilities with the serviceWorkerRegistration JS helper.

- Repository: [Meta/create-react-app](https://github.com/facebook/create-react-app)
- Tags: internals
- Published: 2026-02-26

---

**Service worker registration in Create React App is opt-in and handled by the [`serviceWorkerRegistration.js`](https://github.com/facebook/create-react-app/blob/main/serviceWorkerRegistration.js) helper, which registers the Workbox-generated worker in production only when you call `register()` instead of `unregister()` in [`src/index.js`](https://github.com/facebook/create-react-app/blob/main/src/index.js).**

Create React App (CRA) provides built-in support for Progressive Web Apps through an opt-in service worker architecture. Understanding how service worker registration works is essential for enabling offline capabilities and update management in your React applications. This guide examines the registration flow, key source files, and Workbox integration based on the actual implementation in the `facebook/create-react-app` repository.

## The Opt-In Architecture of CRA PWA Support

CRA deliberately makes PWA functionality optional to avoid caching complications during development. By default, new projects created with the standard template call `serviceWorkerRegistration.unregister()` in [`src/index.js`](https://github.com/facebook/create-react-app/blob/main/src/index.js), which ensures no service worker is active.

To enable PWA features, you must either use the `cra-template-pwa` (or `cra-template-pwa-typescript`) template during project creation, or manually switch the function call from `unregister()` to `register()`. This design ensures that service workers only activate in production builds and never interfere with the development server.

## Key Files in the Service Worker Registration Flow

### src/service-worker.js

This file contains the actual Workbox service worker script that controls caching strategies. During the build process, the `self.__WB_MANIFEST` placeholder is replaced with a precache manifest generated by Workbox. You customize caching behavior by editing this file to add runtime caching routes or adjust precache settings.

### src/serviceWorkerRegistration.js

This helper module exports two primary functions: `register()` and `unregister()`. It handles browser capability detection, production environment validation, and lifecycle event monitoring. The file bridges your application code with the browser's Service Worker API, providing callbacks for update detection and successful installation.

### src/index.js

The application entry point imports the registration helper and decides whether to enable PWA functionality. By default, it imports `serviceWorkerRegistration` and calls `unregister()`. Changing this single line to `register()` activates the service worker in production builds.

## How the Registration Process Works

When you call `serviceWorkerRegistration.register()` in [`src/index.js`](https://github.com/facebook/create-react-app/blob/main/src/index.js), the helper executes a specific validation and registration sequence:

1. **Environment and Support Checks** – The function first verifies `process.env.NODE_ENV === 'production'` and `'serviceWorker' in navigator`. If either check fails, registration aborts silently to prevent development caching or browser incompatibility issues.

2. **Origin Validation** – The helper constructs a URL from `process.env.PUBLIC_URL` and validates that it shares the same origin as the current page. Cross-origin service workers are rejected to prevent security violations.

3. **Worker Registration** – Upon passing validation, the code executes:
   ```javascript
   navigator.serviceWorker
     .register(`${process.env.PUBLIC_URL}/service-worker.js`)
     .then(registration => {
       // Lifecycle monitoring logic
     });
   ```

4. **Lifecycle Event Handling** – The registration promise resolves to a `ServiceWorkerRegistration` object. The helper attaches an `onupdatefound` listener to detect when the browser installs a new service worker version. It then monitors `onstatechange` events on the installing worker to detect when it reaches the `installed` state.

5. **Update Detection** – When `installingWorker.state === 'installed'`, the helper checks `navigator.serviceWorker.controller`. If a controller exists, the new worker is waiting in the background (indicating an update). If no controller exists, this is the first install, and the content is cached for offline use.

## Workbox Integration and Build Process

The service worker registration relies on Workbox integration during the production build. In [`packages/react-scripts/config/webpack.config.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/config/webpack.config.js), CRA includes the `WorkboxWebpackPlugin.InjectManifest` plugin configuration.

During `npm run build`, this plugin:
- Compiles [`src/service-worker.js`](https://github.com/facebook/create-react-app/blob/main/src/service-worker.js) through Webpack
- Replaces the `self.__WB_MANIFEST` placeholder with a JSON array of URLs to precache (generated from the build assets)
- Outputs the final [`service-worker.js`](https://github.com/facebook/create-react-app/blob/main/service-worker.js) to the build directory

The registration URL in [`serviceWorkerRegistration.js`](https://github.com/facebook/create-react-app/blob/main/serviceWorkerRegistration.js) points to `${process.env.PUBLIC_URL}/service-worker.js`, which resolves to this Workbox-generated file in the production deployment.

## Customizing Registration Behavior

The `register()` function accepts an optional `config` object to handle update notifications and successful caching events:

```javascript
serviceWorkerRegistration.register({
  onUpdate: (registration) => {
    // Prompt user to refresh for new content
    const updateConfirmed = window.confirm('New version available. Reload?');
    if (updateConfirmed) {
      window.location.reload();
    }
  },
  onSuccess: (registration) => {
    // Notify that app works offline
    console.log('App is ready for offline use.');
  },
});

```

These callbacks trigger based on the service worker lifecycle states detected during registration.

## Unregistering the Service Worker

To disable PWA functionality, call `unregister()` instead of `register()`:

```javascript
// src/index.js
serviceWorkerRegistration.unregister();

```

This executes:

```javascript
export function unregister() {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.ready
      .then(registration => registration.unregister())
      .catch(error => console.error(error.message));
  }
}

```

Unregistering removes the active service worker, preventing offline caching and background updates. This is the default behavior in CRA to ensure development flexibility.

## Summary

- **CRA PWA support is opt-in** – Service workers only activate when you explicitly call `register()` in [`src/index.js`](https://github.com/facebook/create-react-app/blob/main/src/index.js), preventing caching issues during development.
- **Registration requires production builds** – The [`serviceWorkerRegistration.js`](https://github.com/facebook/create-react-app/blob/main/serviceWorkerRegistration.js) helper checks `process.env.NODE_ENV === 'production'` and browser support before executing `navigator.serviceWorker.register()`.
- **Workbox handles precaching** – During `npm run build`, `WorkboxWebpackPlugin.InjectManifest` compiles [`src/service-worker.js`](https://github.com/facebook/create-react-app/blob/main/src/service-worker.js) and injects the precache manifest at `self.__WB_MANIFEST`.
- **Lifecycle monitoring enables updates** – The registration helper listens for `onupdatefound` and `onstatechange` events to detect new versions and trigger optional `onUpdate` or `onSuccess` callbacks.
- **Unregistering disables the PWA** – Calling `unregister()` removes the service worker, useful for apps that don't require offline functionality.

## Frequently Asked Questions

### How do I enable the service worker in my Create React App project?

To enable service worker registration, open [`src/index.js`](https://github.com/facebook/create-react-app/blob/main/src/index.js) and change `serviceWorkerRegistration.unregister()` to `serviceWorkerRegistration.register()`. This activates the PWA functionality, but only in production builds. The development server (`npm start`) will never register the service worker to prevent caching issues while you code.

### Why is my service worker not registering in development mode?

CRA intentionally disables service worker registration during development. The [`serviceWorkerRegistration.js`](https://github.com/facebook/create-react-app/blob/main/serviceWorkerRegistration.js) helper explicitly checks `process.env.NODE_ENV === 'production'` before calling `navigator.serviceWorker.register()`. This prevents stale caches from interfering with live reloading and debugging. To test the service worker, you must run `npm run build` and serve the production build locally.

### How does Create React App generate the service worker file?

During the production build, CRA uses `WorkboxWebpackPlugin.InjectManifest` (configured in [`packages/react-scripts/config/webpack.config.js`](https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/config/webpack.config.js)) to process [`src/service-worker.js`](https://github.com/facebook/create-react-app/blob/main/src/service-worker.js). The plugin compiles the source file and replaces the `self.__WB_MANIFEST` placeholder with a generated list of URLs to precache. The resulting [`service-worker.js`](https://github.com/facebook/create-react-app/blob/main/service-worker.js) is emitted to the build directory and served from `PUBLIC_URL`.

### How can I notify users when a new version of my PWA is available?

Pass a configuration object with an `onUpdate` callback to the `register()` function in [`src/index.js`](https://github.com/facebook/create-react-app/blob/main/src/index.js). When the service worker detects a new version (when `installingWorker.state` becomes `installed` and `navigator.serviceWorker.controller` exists), it invokes your callback with the registration object. You can use this to display a notification or prompt the user to refresh the page to activate the new version.