Firebase Push Notifications Web

Web push notifications appear in the browser — on desktop or mobile — even when the user is not on your website. Users subscribe once, and you can reach them with updates anytime. Firebase handles the complex browser compatibility and token management layers so you focus on what to send and when.

How Web Push Works

1. User visits your site and grants notification permission
2. Browser generates an FCM token for this browser/user combination
3. You save the token to Firestore
4. Later, your server sends a message to that token via FCM
5. FCM delivers it to the browser via the Service Worker
6. Service Worker displays the notification even if the tab is closed

Step 1 — Create a Service Worker File

Create a file named firebase-messaging-sw.js in your project's public folder (the root that gets served by your web server). The service worker runs in the background and handles notifications when the browser tab is not open.

// public/firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/10.0.0/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.0.0/firebase-messaging-compat.js");

firebase.initializeApp({
  apiKey: "your-api-key",
  authDomain: "your-project.firebaseapp.com",
  projectId: "your-project-id",
  storageBucket: "your-project.appspot.com",
  messagingSenderId: "your-sender-id",
  appId: "your-app-id"
});

const messaging = firebase.messaging();

// Handle background notifications
messaging.onBackgroundMessage((payload) => {
  const { title, body } = payload.notification;
  self.registration.showNotification(title, {
    body,
    icon: "/icon-192x192.png"
  });
});

Step 2 — Initialize FCM in Your App Code

import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { app } from "./firebase";

const messaging = getMessaging(app);

const VAPID_KEY = "your-vapid-key-from-firebase-console";

Step 3 — Request Permission and Get Token

import { doc, setDoc } from "firebase/firestore";
import { db, auth } from "./firebase";

async function requestNotificationPermission() {
  try {
    const permission = await Notification.requestPermission();

    if (permission !== "granted") {
      console.log("Notification permission denied.");
      return;
    }

    // Get the FCM token for this browser
    const token = await getToken(messaging, { vapidKey: VAPID_KEY });

    if (token) {
      console.log("FCM Token:", token);
      // Save the token to Firestore against the user
      await setDoc(
        doc(db, "users", auth.currentUser.uid, "fcm_tokens", token),
        { token, createdAt: new Date(), platform: "web" }
      );
    }
  } catch (error) {
    console.error("Error getting notification permission:", error);
  }
}

// Call this when user clicks "Enable notifications" button
document.getElementById("enable-notif-btn")
  .addEventListener("click", requestNotificationPermission);

Step 4 — Handle Foreground Notifications

When the app tab is open and focused, FCM delivers messages to your app code rather than the service worker. Handle them manually:

onMessage(messaging, (payload) => {
  console.log("Foreground message received:", payload);
  const { title, body } = payload.notification;

  // Show a custom in-app notification banner
  showNotificationBanner(title, body);
});

function showNotificationBanner(title, body) {
  const banner = document.createElement("div");
  banner.textContent = title + ": " + body;
  banner.style.cssText = "position:fixed;top:20px;right:20px;background:#333;color:white;padding:16px;border-radius:8px;z-index:9999;";
  document.body.appendChild(banner);
  setTimeout(() => banner.remove(), 5000);
}

Sending Notifications from a Cloud Function

// functions/index.js
const admin = require("firebase-admin");
admin.initializeApp();

const { onDocumentCreated } = require("firebase-functions/v2/firestore");

// Send notification when a new message is created
exports.sendMessageNotification = onDocumentCreated(
  "chats/{chatId}/messages/{messageId}",
  async (event) => {
    const message = event.data.data();
    const recipientId = message.recipientId;

    // Get all FCM tokens for the recipient
    const tokensSnap = await admin.firestore()
      .collection("users")
      .doc(recipientId)
      .collection("fcm_tokens")
      .get();

    const tokens = tokensSnap.docs.map((d) => d.data().token);
    if (tokens.length === 0) return;

    // Send the notification
    await admin.messaging().sendEachForMulticast({
      tokens,
      notification: {
        title: message.senderName,
        body: message.text
      },
      data: {
        chatId: event.params.chatId
      }
    });
  }
);

Handling Token Refresh

FCM tokens can expire or rotate. Detect token refreshes and update Firestore:

import { onTokenRefreshed } from "./fcm-helpers";
// After getting initial token, watch for refresh
messaging.onTokenRefresh(async () => {
  const newToken = await getToken(messaging, { vapidKey: VAPID_KEY });
  await saveTokenToFirestore(newToken);
});

Key Takeaway

Web push notifications require a service worker file at the root of your public directory, a VAPID key from the Firebase Console, explicit permission from the user, and an FCM token saved to Firestore. Handle foreground messages with onMessage and background messages in the service worker. Send notifications from Cloud Functions by retrieving stored tokens and calling admin.messaging().sendEachForMulticast.

Leave a Comment

Your email address will not be published. Required fields are marked *