![Kate Jeffreys](https://web.dev/images/authors/katejeffreys.jpg) Kate Jeffreys ![Kayce Basques](https://web.dev/images/authors/kaycebasques.jpg) Kayce Basques [X](https://twitter.com/kaycebasques) [GitHub](https://github.com/kaycebasques)

<br />

This codelab shows you, step-by-step, how to build a push notification client.
By the end of the codelab you'll have a client that:

- Subscribes the user to push notifications.
- Receives push messages and displays them as notifications.
- Unsubscribes the user from push notifications.

This codelab is focused on helping you learn by doing and doesn't
talk about concepts much. Read [How push works](https://web.dev/articles/push-notifications-how-push-works.md.txt)
to learn about push notification concepts.

The server code of this codelab is already complete. You'll only be
implementing the client in this codelab. To learn how to implement a
push notification server, check out [Codelab: Build a push notification
server](https://web.dev/articles/push-notifications-server-codelab).

## Browser compatibility

This codelab is known to work with the following operating system and browser combinations:

- Windows: Chrome, Edge
- macOS: Chrome, Firefox
- Android: Chrome, Firefox

This codelab is known to **not** work with the following operating systems
(or operating system and browser combinations):

- macOS: Brave, Edge, Safari
- iOS

## Setup

### Set up authentication

Before you can get push notifications working, you need to set up
your server and client with authentication keys.
See [Sign your web push protocol requests](https://web.dev/articles/push-notifications-overview.md.txt#sign)
to learn why. Typically, you store secrets in a `.env` file, similar to this.

```text
VAPID_PUBLIC_KEY="BKiwTvD9HA..."
VAPID_PRIVATE_KEY="4mXG9jBUaU..."
VAPID_SUBJECT="mailto:test@test.test"
```

1. Open `public/index.js`.
2. Replace `VAPID_PUBLIC_KEY_VALUE_HERE` with the value of your public key.

## Register a service worker

Your client needs a service worker to receive and display
notifications. It's best to register the service worker as early as possible.
See [Receive and display the pushed messages as
notifications](https://web.dev/articles/push-notifications-overview#notification) for more context.

1. Replace the `// TODO add startup logic here` comment with the following code:

       if ('serviceWorker' in navigator && 'PushManager' in window) {
         navigator.serviceWorker.register('./service-worker.js').then(serviceWorkerRegistration => {
           console.info('Service worker was registered.');
           console.info({serviceWorkerRegistration});
         }).catch(error => {
           console.error('An error occurred while registering the service worker.');
           console.error(error);
         });
         subscribeButton.disabled = false;
       } else {
         console.error('Browser does not support service workers or push messages.');
       }

       subscribeButton.addEventListener('click', subscribeButtonHandler);
       unsubscribeButton.addEventListener('click', unsubscribeButtonHandler);

2. In Chrome, [Open DevTools Console](https://developer.chrome.com/docs/devtools/console/reference#open).

3. You should see the message `Service worker was registered.` logged to
   the Console.

## Request push notification permission

You should never request permission to send push notifications on page load.
Instead, your UI should ask the user if they want to receive push notifications.
Once they explicitly confirm (with a button click, for example) then you can
start the formal process for getting push notification permission from the browser.

1. In `public/index.js` replace the `// TODO` comment in
   `subscribeButtonHandler()` with the following code:

       // Prevent the user from clicking the subscribe button multiple times.
       subscribeButton.disabled = true;
       const result = await Notification.requestPermission();
       if (result === 'denied') {
         console.error('The user explicitly denied the permission request.');
         return;
       }
       if (result === 'granted') {
         console.info('The user accepted the permission request.');
       }

2. Go back to the app tab and click **Subscribe to push**. Your browser or
   operating system will ask if you want to let the website send you push
   notifications.

3. Select **Allow** (or the equivalent phrase used by your browser or operating
   system). In the Console you should see a message indicating whether the
   request was accepted or denied.

> [!TIP]
> **Tip:** If you're in an incognito or guest window, your browser may deny the request automatically. Keep an eye out for any browser UI indicating that the request was blocked automatically.

## Subscribe to push notifications

The [subscription process](https://web.dev/articles/push-notifications-overview.md.txt#subscription)
involves interacting with a web service controlled
by the browser vendor that's called a **push service**. Once you get
the push notification subscription information you need to send it to a server
and have the server store it in a database long-term.

1. Add the following highlighted code to `subscribeButtonHandler()`:

       subscribeButton.disabled = true;
       const result = await Notification.requestPermission();
       if (result === 'denied') {
         console.error('The user explicitly denied the permission request.');
         return;
       }
       if (result === 'granted') {
         console.info('The user accepted the permission request.');
       }
       const registration = await navigator.serviceWorker.getRegistration();
       const subscribed = await registration.pushManager.getSubscription();
       if (subscribed) {
         console.info('User is already subscribed.');
         notifyMeButton.disabled = false;
         unsubscribeButton.disabled = false;
         return;
       }
       const subscription = await registration.pushManager.subscribe({
         userVisibleOnly: true,
         applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY)
       });
       notifyMeButton.disabled = false;
       fetch('/add-subscription', {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json'
         },
         body: JSON.stringify(subscription)
       });

The `userVisibleOnly` option must be `true`. It may one day be possible
to push messages without displaying user-visible notifications
(**silent pushes**) but browsers don't allow that capability,
because of privacy concerns.

The `applicationServerKey` value relies on a utility function that
converts a base64 string to a Uint8Array. This value is used for
authentication between your server and the push service.

## Unsubscribe from push notifications

After a user has subscribed to push notifications, your UI needs to
provide a way to unsubscribe in case the user changes their mind
and no longer wants to receive push notifications.

- Replace the `// TODO` comment in `unsubscribeButtonHandler()` with the following code:

    const registration = await navigator.serviceWorker.getRegistration();
    const subscription = await registration.pushManager.getSubscription();
    fetch('/remove-subscription', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({endpoint: subscription.endpoint})
    });
    const unsubscribed = await subscription.unsubscribe();
    if (unsubscribed) {
      console.info('Successfully unsubscribed from push notifications.');
      unsubscribeButton.disabled = true;
      subscribeButton.disabled = false;
      notifyMeButton.disabled = true;
    }

## Receive a push message and display it as a notification

As mentioned before, you need a service worker to handle the
receiving and displaying of messages that were pushed to the client
from your server. See [Receive and display the pushed messages as
notifications](https://web.dev/articles/push-notifications-overview#notification) for more detail.

1. Open `public/service-worker.js` and replace the `// TODO` comment
   in the service worker's `push` event handler with the following code:

       let data = event.data.json();
       const image = 'logo.png';
       const options = {
         body: data.options.body,
         icon: image
       }
       self.registration.showNotification(
         data.title,
         options
       );

2. Go back to the app tab.

3. Click **Notify me**. You should receive a push notification.

4. Open the URL of your app tab in other supported browsers. Go through the
   subscription workflow, and click **Notify all**. You should receive the same
   push notification in each.

You can customize the notification in lots of ways. See the parameters of
[`ServiceWorkerRegistration.showNotification()`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerRegistration/showNotification) to learn more.

> [!TIP]
> **Tip:** Read about the [Skip waiting phase](https://web.dev/articles/service-worker-lifecycle.md.txt#skip_the_waiting_phase), in your service worker's `install` listener. Without it, the code changes that you make to your service worker wouldn't take effect immediately.

## Open a URL when a user clicks a notification

In the real-world, you'll probably use the notification as a way
to re-engage your user and prompt them to visit your site.
To do that, you need to configure your service worker a bit more.

1. Replace the `// TODO` comment in the service worker's `notificationclick`
   event handler with the following code:

       event.notification.close();
       event.waitUntil(self.clients.openWindow('https://web.dev'));

2. Go back to the app tab, send yourself another notification, and then
   click the notification. Your browser should open a new tab and load
   `https://web.dev`.

## Next steps

- Look at [`ServiceWorkerRegistration.showNotification()`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerRegistration/showNotification) to discover all of the different ways you can customize notifications.
- Read [Push notifications overview](https://web.dev/articles/push-notifications-overview.md.txt) for a deeper conceptual understanding of how push notifications work.
- Check out [Codelab: Build a push notification server](https://web.dev/articles/push-notifications-server-codelab.md.txt) to learn how to build a server that manages subscriptions and sends web push protocol requests.
- Try out [Notification Generator](https://tests.peter.sh/notification-generator/) to test out all the ways you can customize notifications.