Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Send Notifications to a Topic in Firebase

Firebase Cloud Messaging (FCM) topics let you send notifications to groups of users who subscribe to a named topic. Subscribe devices on the client with getToken() and subscribe server-side using the Admin SDK's subscribeToTopic(). Send notifications to a topic from a Cloud Function using admin.messaging().send() with the topic field. Topics are ideal for broadcast messages like news alerts, feature updates, or category-based notifications.

What you'll learn

  • How to subscribe users to FCM topics from the server
  • How to send notifications to a topic using the Admin SDK
  • How to use topic conditions for targeting multiple topics
  • How to handle incoming topic notifications on the client
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read15-20 minFirebase Cloud Messaging, Firebase Admin SDK, Blaze plan for Cloud FunctionsMarch 2026RapidDev Engineering Team
TL;DR

Firebase Cloud Messaging (FCM) topics let you send notifications to groups of users who subscribe to a named topic. Subscribe devices on the client with getToken() and subscribe server-side using the Admin SDK's subscribeToTopic(). Send notifications to a topic from a Cloud Function using admin.messaging().send() with the topic field. Topics are ideal for broadcast messages like news alerts, feature updates, or category-based notifications.

Sending Topic Notifications with Firebase Cloud Messaging

FCM topics provide a publish-subscribe model for notifications. Instead of tracking individual device tokens, you subscribe users to named topics and send messages to all subscribers at once. This tutorial covers setting up FCM, subscribing devices to topics via the Admin SDK, sending notification and data messages to topics from Cloud Functions, using topic conditions for complex targeting, and handling received notifications on the web client.

Prerequisites

  • A Firebase project on the Blaze plan (for Cloud Functions)
  • Firebase Cloud Messaging enabled in your project
  • Firebase Admin SDK installed in your Cloud Functions
  • A web app with the Firebase JS SDK configured and notification permissions granted

Step-by-step guide

1

Get the device registration token on the client

Before subscribing a user to a topic, you need their FCM registration token. On the web, request notification permission and call getToken() from the firebase/messaging module. This returns a unique token for the browser instance. Store this token in Firestore linked to the user's UID so your server can manage subscriptions. The token can change when the user clears browser data or the service worker updates, so refresh it on each app load.

typescript
1import { getMessaging, getToken } from 'firebase/messaging';
2import { getFirestore, doc, setDoc } from 'firebase/firestore';
3import { getAuth } from 'firebase/auth';
4
5const messaging = getMessaging();
6const db = getFirestore();
7
8async function requestNotificationPermission() {
9 const permission = await Notification.requestPermission();
10 if (permission !== 'granted') {
11 console.log('Notification permission denied');
12 return null;
13 }
14
15 const token = await getToken(messaging, {
16 vapidKey: 'YOUR_VAPID_KEY'
17 });
18
19 // Store token in Firestore for server-side subscription
20 const user = getAuth().currentUser;
21 if (user && token) {
22 await setDoc(doc(db, 'fcmTokens', user.uid), {
23 token,
24 updatedAt: new Date()
25 });
26 }
27
28 return token;
29}

Expected result: The browser returns an FCM registration token that is stored in Firestore for server-side use.

2

Subscribe users to topics from the server

Topic subscriptions are managed server-side using the Firebase Admin SDK. Call messaging().subscribeToTopic() with an array of registration tokens and a topic name. Topics are created automatically when the first token subscribes. Use Cloud Functions to manage subscriptions, for example by triggering on user preferences changes in Firestore. Topic names can contain letters, numbers, underscores, and hyphens.

typescript
1import { initializeApp } from 'firebase-admin/app';
2import { getMessaging } from 'firebase-admin/messaging';
3import { getFirestore } from 'firebase-admin/firestore';
4import { onDocumentCreated } from 'firebase-functions/v2/firestore';
5import { logger } from 'firebase-functions/v2';
6
7initializeApp();
8const messaging = getMessaging();
9const db = getFirestore();
10
11// Subscribe a user to a topic when they update their preferences
12export const manageTopicSubscription = onDocumentCreated(
13 'userPreferences/{userId}',
14 async (event) => {
15 const userId = event.params.userId;
16 const prefs = event.data?.data();
17
18 // Get the user's FCM token from Firestore
19 const tokenDoc = await db.collection('fcmTokens').doc(userId).get();
20 const token = tokenDoc.data()?.token;
21
22 if (!token) {
23 logger.warn('No FCM token found for user', { userId });
24 return;
25 }
26
27 // Subscribe to selected topics
28 const topics = prefs?.subscribedTopics || [];
29 for (const topic of topics) {
30 await messaging.subscribeToTopic([token], topic);
31 logger.info('Subscribed to topic', { userId, topic });
32 }
33 }
34);

Expected result: User devices are subscribed to the specified FCM topics and will receive messages sent to those topics.

3

Send a notification to a topic

Use the Admin SDK's messaging().send() method with a topic field to deliver a notification to all subscribers. The message can include a notification payload (shown in the system tray) and a data payload (processed by your app code). Create an HTTP callable function that your admin dashboard can call to send topic notifications.

typescript
1import { onCall, HttpsError } from 'firebase-functions/v2/https';
2import { getMessaging } from 'firebase-admin/messaging';
3import { logger } from 'firebase-functions/v2';
4
5const messaging = getMessaging();
6
7export const sendTopicNotification = onCall(async (request) => {
8 // Only allow admin users to send notifications
9 if (!request.auth?.token?.admin) {
10 throw new HttpsError('permission-denied', 'Admin access required');
11 }
12
13 const { topic, title, body, data } = request.data;
14
15 const message = {
16 topic: topic,
17 notification: {
18 title: title,
19 body: body
20 },
21 data: data || {},
22 webpush: {
23 notification: {
24 icon: '/icon-192.png',
25 click_action: 'https://your-app.com/notifications'
26 }
27 }
28 };
29
30 try {
31 const response = await messaging.send(message);
32 logger.info('Topic notification sent', { topic, messageId: response });
33 return { success: true, messageId: response };
34 } catch (error: any) {
35 logger.error('Failed to send topic notification', {
36 topic,
37 error: error.message
38 });
39 throw new HttpsError('internal', 'Failed to send notification');
40 }
41});

Expected result: All devices subscribed to the topic receive the notification.

4

Use topic conditions for complex targeting

FCM supports condition expressions to target users subscribed to combinations of topics. Conditions use logical operators: && (AND), || (OR), and ! (NOT). For example, send a notification to users subscribed to 'sports' AND 'news' but NOT 'promotions'. Conditions support up to 5 topics in a single expression.

typescript
1import { getMessaging } from 'firebase-admin/messaging';
2
3const messaging = getMessaging();
4
5// Send to users subscribed to 'sports' AND 'news'
6async function sendConditionalNotification() {
7 const message = {
8 condition: "'sports' in topics && 'news' in topics",
9 notification: {
10 title: 'Sports News Update',
11 body: 'Check out the latest sports headlines'
12 }
13 };
14
15 await messaging.send(message);
16}
17
18// Send to 'deals' subscribers who are NOT in 'opted-out'
19async function sendDealsNotification() {
20 const message = {
21 condition: "'deals' in topics && !('opted-out' in topics)",
22 notification: {
23 title: 'New Deal Available',
24 body: 'A new exclusive deal has been posted'
25 }
26 };
27
28 await messaging.send(message);
29}

Expected result: Only users matching the topic condition receive the notification.

5

Handle incoming notifications on the client

Set up a message handler on the client to process incoming notifications when the app is in the foreground. For background notifications, configure a service worker. Foreground notifications are not automatically displayed in the system tray, so you need to show them manually using the Notification API or an in-app notification component.

typescript
1import { getMessaging, onMessage } from 'firebase/messaging';
2
3const messaging = getMessaging();
4
5// Handle foreground notifications
6onMessage(messaging, (payload) => {
7 console.log('Foreground notification:', payload);
8
9 // Show browser notification manually
10 if (payload.notification) {
11 new Notification(payload.notification.title || 'New message', {
12 body: payload.notification.body,
13 icon: '/icon-192.png'
14 });
15 }
16
17 // Handle data payload
18 if (payload.data) {
19 console.log('Data payload:', payload.data);
20 // Update your app state based on the data
21 }
22});
23
24// For background notifications, add this to firebase-messaging-sw.js:
25// importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-app-compat.js');
26// importScripts('https://www.gstatic.com/firebasejs/10.12.0/firebase-messaging-compat.js');
27// firebase.initializeApp({ ... });
28// const messaging = firebase.messaging();
29// messaging.onBackgroundMessage((payload) => {
30// self.registration.showNotification(payload.notification.title, {
31// body: payload.notification.body
32// });
33// });

Expected result: Foreground notifications are displayed in the app and background notifications appear in the system tray.

Complete working example

topic-notifications.ts
1// Cloud Function: Send topic notifications (functions/src/notifications.ts)
2import { initializeApp } from 'firebase-admin/app';
3import { getMessaging } from 'firebase-admin/messaging';
4import { getFirestore } from 'firebase-admin/firestore';
5import { onCall, HttpsError } from 'firebase-functions/v2/https';
6import { onDocumentWritten } from 'firebase-functions/v2/firestore';
7import { logger } from 'firebase-functions/v2';
8
9initializeApp();
10const messaging = getMessaging();
11const db = getFirestore();
12
13// Subscribe user to topics based on preferences
14export const syncTopicSubscriptions = onDocumentWritten(
15 'userPreferences/{userId}',
16 async (event) => {
17 const userId = event.params.userId;
18 const before = event.data?.before?.data();
19 const after = event.data?.after?.data();
20
21 const tokenDoc = await db.collection('fcmTokens').doc(userId).get();
22 const token = tokenDoc.data()?.token;
23 if (!token) return;
24
25 const oldTopics: string[] = before?.topics || [];
26 const newTopics: string[] = after?.topics || [];
27
28 // Unsubscribe from removed topics
29 const removed = oldTopics.filter(t => !newTopics.includes(t));
30 for (const topic of removed) {
31 await messaging.unsubscribeFromTopic([token], topic);
32 logger.info('Unsubscribed from topic', { userId, topic });
33 }
34
35 // Subscribe to new topics
36 const added = newTopics.filter(t => !oldTopics.includes(t));
37 for (const topic of added) {
38 await messaging.subscribeToTopic([token], topic);
39 logger.info('Subscribed to topic', { userId, topic });
40 }
41 }
42);
43
44// Send notification to a topic (admin only)
45export const sendTopicNotification = onCall(async (request) => {
46 if (!request.auth?.token?.admin) {
47 throw new HttpsError('permission-denied', 'Admin required');
48 }
49
50 const { topic, title, body, link } = request.data;
51 const response = await messaging.send({
52 topic,
53 notification: { title, body },
54 data: { link: link || '/' },
55 webpush: {
56 fcmOptions: { link: link || '/' },
57 notification: { icon: '/icon-192.png' }
58 }
59 });
60
61 logger.info('Notification sent', { topic, messageId: response });
62 return { messageId: response };
63});

Common mistakes when sending Notifications to a Topic in Firebase

Why it's a problem: Trying to subscribe to topics from the client SDK on the web

How to avoid: Web clients cannot subscribe to topics directly. Use the Firebase Admin SDK server-side to call subscribeToTopic() with the client's registration token. Mobile SDKs (iOS/Android) support client-side topic subscription.

Why it's a problem: Not handling token refresh, leading to stale tokens in topic subscriptions

How to avoid: FCM tokens can change. Listen for token refresh events and update the stored token in Firestore. Re-subscribe the new token to the same topics.

Why it's a problem: Sending notifications without checking if the user has admin permissions

How to avoid: Always verify the caller's permissions before sending topic notifications. Use custom claims (auth.token.admin) or Firestore role checks in your Cloud Function.

Best practices

  • Manage topic subscriptions server-side with the Admin SDK for consistent state management
  • Store FCM tokens in Firestore linked to user UIDs for easy server-side access
  • Use topic conditions for targeting intersections and exclusions of multiple topics
  • Include both notification and data payloads so foreground and background scenarios are handled
  • Handle token refresh events and re-subscribe new tokens to the same topics
  • Limit topic names to descriptive, lowercase strings with hyphens (like 'sports-news')
  • Validate send permissions in Cloud Functions before dispatching notifications
  • Log all notification sends with topic name and message ID for debugging delivery issues

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Walk me through implementing Firebase Cloud Messaging topic notifications for a web app. Show me how to get the device token, subscribe to topics server-side with the Admin SDK, send notifications to a topic from a Cloud Function, and handle incoming messages on the client.

Firebase Prompt

Write Firebase Cloud Functions v2 in TypeScript that manage FCM topic subscriptions based on user preferences stored in Firestore, and provide an admin-only callable function to send notifications to topics. Include both notification and data payloads.

Frequently asked questions

How many topics can a single device subscribe to?

A single device can be subscribed to up to 2,000 topics. There is no limit on the number of subscribers per topic. For most applications, a few dozen topics per user is typical.

Is there a limit on how many devices can subscribe to a topic?

No. There is no upper limit on the number of devices subscribed to a single topic. Topics scale automatically to handle millions of subscribers.

Can I send topic notifications from the Firebase Console?

Yes. Go to Firebase Console > Cloud Messaging > New Notification. You can target by topic in the Target section. However, for production use, sending from Cloud Functions via the Admin SDK gives you more control over the payload and conditions.

What is the difference between notification and data messages?

Notification messages (notification payload) are displayed automatically by the system when the app is in the background. Data messages (data payload) are always handled by your app code. You can include both in a single message for full control over foreground and background behavior.

How fast are topic messages delivered?

Topic messages are typically delivered within a few seconds to most subscribers. However, FCM does not guarantee delivery order or exact timing. Large fan-out operations to millions of subscribers may take up to a few minutes to complete.

Can I unsubscribe a user from a topic?

Yes. Use messaging.unsubscribeFromTopic([token], topicName) with the Admin SDK. You should unsubscribe when a user changes their notification preferences or deletes their account.

Can RapidDev help build a notification system with Firebase topics?

Yes. RapidDev can architect and implement a complete notification system with topic management, user preference sync, admin send interface, notification history tracking, and delivery analytics using Firebase Cloud Messaging.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.