Send push notifications with Firebase Cloud Messaging (FCM) by setting up a service worker on the web client to receive messages, requesting notification permission to get a device token, and sending messages from a Cloud Function or the Admin SDK using that token. FCM supports three message types: notification messages (handled by the OS), data messages (handled by your app), and combined messages. Use topics to send to groups of users and the Admin SDK for server-side sending.
Sending Push Notifications with Firebase Cloud Messaging
Firebase Cloud Messaging (FCM) lets you send push notifications to web and mobile devices for free with no message limits. This tutorial covers the end-to-end flow: setting up FCM in your web app, requesting permission and obtaining device tokens, sending messages server-side with the Admin SDK, and handling notifications in both foreground and background states. You will also learn how to use topics for group messaging.
Prerequisites
- A Firebase project with Cloud Messaging enabled
- Firebase JS SDK v9+ installed in your web app
- A web app key pair generated in Firebase Console (Project Settings > Cloud Messaging > Web Push certificates)
- Firebase Cloud Functions initialized (for server-side sending)
Step-by-step guide
Set up the Firebase Messaging service worker
Set up the Firebase Messaging service worker
FCM requires a service worker file named firebase-messaging-sw.js in your public root directory. This file handles background notifications when your app is not in the foreground. The service worker initializes Firebase and registers the messaging service. Without this file, background notifications will not work.
1// public/firebase-messaging-sw.js2importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js')3importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-messaging-compat.js')45firebase.initializeApp({6 apiKey: 'YOUR_API_KEY',7 authDomain: 'YOUR_PROJECT.firebaseapp.com',8 projectId: 'YOUR_PROJECT_ID',9 messagingSenderId: 'YOUR_SENDER_ID',10 appId: 'YOUR_APP_ID'11})1213const messaging = firebase.messaging()1415messaging.onBackgroundMessage((payload) => {16 console.log('Background message:', payload)17 const { title, body, icon } = payload.notification ?? {}18 self.registration.showNotification(title ?? 'New notification', {19 body: body ?? '',20 icon: icon ?? '/icon-192.png'21 })22})Expected result: The service worker registers on page load and handles background notifications when the app is not in the active tab.
Request notification permission and get the device token
Request notification permission and get the device token
Before sending notifications to a user, you must request their permission and obtain a device token. Use the Notification API to request permission, then call getToken() from Firebase Messaging with your VAPID key to get the token. Store this token in Firestore linked to the user's UID so you can target notifications to specific users.
1import { getMessaging, getToken } from 'firebase/messaging'2import { getFirestore, doc, setDoc } from 'firebase/firestore'3import { getAuth } from 'firebase/auth'45const messaging = getMessaging()6const db = getFirestore()78async function requestNotificationPermission() {9 const permission = await Notification.requestPermission()1011 if (permission !== 'granted') {12 console.log('Notification permission denied.')13 return null14 }1516 const token = await getToken(messaging, {17 vapidKey: 'YOUR_VAPID_KEY_FROM_FIREBASE_CONSOLE'18 })1920 // Store the token linked to the user21 const user = getAuth().currentUser22 if (user && token) {23 await setDoc(24 doc(db, 'fcmTokens', user.uid),25 {26 token,27 userId: user.uid,28 updatedAt: new Date(),29 platform: 'web'30 },31 { merge: true }32 )33 }3435 console.log('FCM token:', token)36 return token37}Expected result: The browser shows a permission dialog. If granted, a device token is returned and stored in Firestore for later use.
Handle foreground messages in the app
Handle foreground messages in the app
When your app is in the foreground (active tab), FCM delivers messages to your JavaScript code via the onMessage() callback instead of showing a system notification. Handle the message in your app code to show an in-app notification, toast, or badge update. You can optionally show a native notification from the foreground handler.
1import { getMessaging, onMessage } from 'firebase/messaging'23const messaging = getMessaging()45onMessage(messaging, (payload) => {6 console.log('Foreground message:', payload)78 const { title, body } = payload.notification ?? {}910 // Option 1: Show a native notification manually11 if (Notification.permission === 'granted') {12 new Notification(title ?? 'New message', {13 body: body ?? '',14 icon: '/icon-192.png'15 })16 }1718 // Option 2: Show an in-app toast or update UI19 // showToast({ title, body })20})Expected result: When a message arrives while the app is open, the callback fires with the full message payload, enabling custom in-app notification handling.
Send notifications from a Cloud Function
Send notifications from a Cloud Function
Use the Firebase Admin SDK in a Cloud Function to send notifications server-side. You can send to a specific device token, a topic, or multiple tokens. Create an HTTPS callable function that accepts the target token and message content, then use admin.messaging().send() to deliver the notification.
1import { onCall, HttpsError } from 'firebase-functions/v2/https'2import { initializeApp } from 'firebase-admin/app'3import { getMessaging } from 'firebase-admin/messaging'4import { getFirestore } from 'firebase-admin/firestore'56initializeApp()78export const sendNotification = onCall(async (request) => {9 if (!request.auth) {10 throw new HttpsError('unauthenticated', 'Must be signed in.')11 }1213 const { targetUserId, title, body } = request.data1415 // Get the target user's FCM token from Firestore16 const tokenDoc = await getFirestore()17 .doc(`fcmTokens/${targetUserId}`)18 .get()1920 if (!tokenDoc.exists) {21 throw new HttpsError('not-found', 'User has no registered device.')22 }2324 const { token } = tokenDoc.data()!2526 const message = {27 notification: { title, body },28 data: { targetUserId, senderId: request.auth.uid },29 token30 }3132 try {33 const response = await getMessaging().send(message)34 return { success: true, messageId: response }35 } catch (error: any) {36 if (error.code === 'messaging/registration-token-not-registered') {37 // Token is stale — delete it38 await getFirestore().doc(`fcmTokens/${targetUserId}`).delete()39 }40 throw new HttpsError('internal', error.message)41 }42})Expected result: The Cloud Function sends a push notification to the target user's device and returns the message ID on success.
Send notifications to a topic for group messaging
Send notifications to a topic for group messaging
Topics let you send a single message to all devices subscribed to that topic, without managing individual tokens. Subscribe users to topics server-side using the Admin SDK, then send messages to the topic. Topics are ideal for broadcast messages like announcements, new content alerts, or category-based notifications.
1import { getMessaging } from 'firebase-admin/messaging'23const messaging = getMessaging()45// Subscribe a user's token to a topic (server-side)6async function subscribeToTopic(token: string, topic: string) {7 await messaging.subscribeToTopic(token, topic)8}910// Send to all subscribers of a topic11async function sendToTopic(topic: string, title: string, body: string) {12 const message = {13 notification: { title, body },14 topic: topic // e.g., 'news', 'deals', 'product-updates'15 }1617 const response = await messaging.send(message)18 console.log('Sent to topic:', response)19 return response20}2122// Send to multiple topics with a condition23async function sendToCondition(title: string, body: string) {24 const message = {25 notification: { title, body },26 condition: "'deals' in topics && 'electronics' in topics"27 }2829 const response = await messaging.send(message)30 return response31}Expected result: All devices subscribed to the specified topic receive the notification. Condition-based sends target users subscribed to multiple topics.
Complete working example
1import { initializeApp } from 'firebase/app'2import { getMessaging, getToken, onMessage, MessagePayload } from 'firebase/messaging'3import { getFirestore, doc, setDoc } from 'firebase/firestore'4import { getAuth } from 'firebase/auth'56const app = initializeApp({7 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY!,8 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN!,9 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!,10 messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID!,11 appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID!12})1314const messaging = getMessaging(app)15const db = getFirestore(app)1617export async function initNotifications(): Promise<string | null> {18 const permission = await Notification.requestPermission()19 if (permission !== 'granted') return null2021 const token = await getToken(messaging, {22 vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY!23 })2425 const user = getAuth().currentUser26 if (user && token) {27 await setDoc(28 doc(db, 'fcmTokens', user.uid),29 { token, userId: user.uid, updatedAt: new Date(), platform: 'web' },30 { merge: true }31 )32 }3334 return token35}3637export function onForegroundMessage(38 callback: (payload: MessagePayload) => void39) {40 return onMessage(messaging, callback)41}4243export async function refreshToken(): Promise<string | null> {44 try {45 const token = await getToken(messaging, {46 vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY!47 })4849 const user = getAuth().currentUser50 if (user && token) {51 await setDoc(52 doc(db, 'fcmTokens', user.uid),53 { token, updatedAt: new Date() },54 { merge: true }55 )56 }57 return token58 } catch (error) {59 console.error('Token refresh failed:', error)60 return null61 }62}Common mistakes when sending Push Notifications with Firebase
Why it's a problem: Missing the firebase-messaging-sw.js service worker file, causing background notifications to silently fail
How to avoid: Create firebase-messaging-sw.js in your public root directory with Firebase initialization and the onBackgroundMessage handler. Without this file, only foreground messages work.
Why it's a problem: Using a stale device token that has been invalidated, causing 'messaging/registration-token-not-registered' errors
How to avoid: Handle this error by deleting the stale token from Firestore. Refresh tokens periodically by calling getToken() on app launch and updating the stored value.
Why it's a problem: Not requesting notification permission before calling getToken(), causing a silent failure
How to avoid: Always call Notification.requestPermission() first and check that the result is 'granted' before calling getToken(). Browsers require explicit user consent.
Why it's a problem: Storing the VAPID key in the service worker file, which is publicly accessible
How to avoid: The VAPID key is safe to expose publicly — it is a public key used for push protocol identification, not a secret. However, keep your Admin SDK credentials (used for sending) strictly server-side.
Best practices
- Store device tokens in Firestore linked to user UIDs so you can target notifications to specific users
- Refresh tokens on every app launch by calling getToken() and updating Firestore to keep tokens current
- Handle stale tokens by catching 'messaging/registration-token-not-registered' and removing the invalid token from storage
- Use topics for broadcast messaging to avoid managing individual tokens for large user groups
- Include both notification and data payloads so your app can display rich notifications in both foreground and background
- Send notifications from Cloud Functions, never from the client — the Admin SDK credentials must stay server-side
- Request notification permission at a meaningful moment (after sign-up, before a relevant action), not immediately on page load
- Test with the Firebase Console Messaging Composer before writing Cloud Functions to verify your setup works
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to send push notifications in my Firebase web app. Show me the complete setup: firebase-messaging-sw.js service worker, requesting permission and getting a device token, storing tokens in Firestore, sending notifications from a Cloud Function using the Admin SDK, and handling messages in both foreground and background. Use Firebase modular SDK v9+ and Cloud Functions v2.
Build a complete Firebase push notification system for a web app with: service worker setup, permission request and token storage in Firestore, a Cloud Function to send notifications to specific users by UID, topic-based group messaging, foreground message handling with onMessage, and stale token cleanup. Use TypeScript with Firebase modular SDK v9+ and Cloud Functions v2.
Frequently asked questions
Is Firebase Cloud Messaging free?
Yes. FCM is completely free with no message limits. You can send unlimited notifications to unlimited devices. The only cost is if you use Cloud Functions to send messages (which requires the Blaze plan), but Cloud Functions has a free tier of 2 million invocations per month.
What is the difference between notification messages and data messages?
Notification messages are handled by the OS and displayed automatically in the system tray when the app is in the background. Data messages are delivered silently to your app code for custom handling. You can combine both in a single message — the notification appears in the background, and the data payload is available when the user taps it.
Why are my background notifications not showing?
The most common cause is a missing or misconfigured firebase-messaging-sw.js service worker. Verify the file exists at the root of your public directory, contains the correct Firebase config, and is being registered by the browser. Check the browser's Application > Service Workers panel in DevTools.
How do I send notifications to multiple devices?
Use the Admin SDK's sendEachForMulticast() method with an array of tokens, or use topics to send to all subscribed devices with a single message. Topics are simpler for broadcast scenarios; token arrays give you fine-grained control.
Do push notifications work on iOS Safari?
Yes, as of iOS 16.4+ and Safari 16.4+. The web app must be added to the Home Screen as a Progressive Web App (PWA). Standard Safari tabs do not support Web Push on iOS. Use a web app manifest and register a service worker.
Can RapidDev help implement push notifications in my Firebase application?
Yes. RapidDev can build a complete notification system including FCM setup, token management, Cloud Function sending logic, topic-based messaging, and notification UI handling for web and mobile.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation