Handling Firebase Notification Messages in Your Web App

December 1, 2017

Firebase Cloud Messaging (FCM) is a free cross-platform messaging solution that lets you deliver push notifications and data messages to your client app. While the documentation distinguishes between "notification messages" and "data messages", it is not always clear how the two types are handled by the client.

For detailed instructions on how to implement FCM and its JavaScript SDK, check out the official documentation. This article will focus on where and how the message are handled. We'll be using the "legacy" protocol instead of the newer "v1" HTTP protocol, but the concepts described are applicable to both.

The first thing to note is that notification messages and data messages are handled differently by the Firebase JavaScript SDK. Notification messages are displayed automatically on behalf of your app, and the SDK also provides the necessary functionality for clicking and closing the notification. Data messages, however, must be handled explicitly by you, the developer, as they are not automatically displayed and consequently provide no clicking and closing functionality.

As a wrapper on top of the Web Push API, the Firebase Messaging library provides "notification messages" as a convenience that handles the details of displaying push notifications to your users. FCM can also be used without them, as you are free to use data messages instead and utilize the Web Push API as you see fit.

What Makes a Notification?

A message is considered a notification if it contains a notification key:

{
  "message": {
    "token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "notification": {
      "title": "Portugal vs. Denmark",
      "body": "Great match!",
      "click_action": "https://example.com/matches/123"
    }
  }
}

The click_action key is used by the Firebase SDK to decide what to do when the notification is clicked. Without it, clicking the notification will lead nowhere.

A data message does not have the notification key, but contains a data key instead:

{
  "message": {
    "token": "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "data": {
      "Nick": "Mario",
      "body": "Great match!",
      "Room": "PortugalVSDenmark"
    }
  }
}

Because it lacks a notification key, the above message must be handled manually by your app. The Firebase documentation recommends that you always add a notification key so that you don't need to handle the displaying of the message yourself. If you want to add arbitrary data to a notification, it's perfectly valid to include a notification key and a data key; Firebase will still treat it as a notification message and display it, and the extra data can be handled (manually) by your app.

How Data Messages and Notification Messages Are Handled

A message sent by FCM will be handled in one or two places depending on its type: in your service worker by the callback passed to setBackgroundMessageHandler(), and in your client code by the callback passed to onMessage().

onMessage is always called when your app is running in the foreground (when the browser window and tab is in focus), both when receiving notification messages and data messages. The callback is invoked with the message payload which you can read and handle as you'd like.

import firebase from 'firebase';

const messaging = firebase.messaging();

messaging.onMessage(function(payload) {
  console.log("Message received. ", payload);
  // ...
});

onBackgroundMessageHandler is called when your app is running in the background and the message does not contain a notification key. In other words, this handler is only for data messages.

// In your service worker
messaging.setBackgroundMessageHandler(function(payload) {
  console.log('[firebase-messaging-sw.js] Received background message ', payload);
  // ...
});

Because the background handler is not called when you send a notification message, you cannot use it to customize the message's behavior. The way to do that is to omit the notification key and implement everything yourself, including click actions, inside the setBackgroundMessageHandler callback.

What happens if your app receives a notification message while it is running in the background? The notification is displayed and neither handler will run. But, if you click on the notification, the custom data payload (remember, you can always include both a notification key and a payload key) is sent to the onMessage handler. The notification key, though, is removed from the payload. This is all handled by the Firebase SDK.

messaging.onMessage(function(payload) {
  // Messages received. Either because the
  // app is running in the foreground, or
  // because the notification was clicked.
  // `payload` will contain your data.
  console.log("Message received. ", payload);
});

It is also worth noting that notifications sent through the Firebase Console will indeed contain a notification key and can therefore not be intercepted by setBackgroundMessageHandler.

The relationship between message types and handlers can be summarized in a table:

App in foreground App in background
Data message onMessage setBackgroundMessageHandler
Notification message onMessage notification is displayed; onMessage runs when clicked

Also check out Device Group Management With Firebase Cloud Messaging