Stay organized with collectionsSave and categorize content based on your preferences.
Based on the publish/subscribe model,FCMtopic messaging allows you to send a message
to multiple devices that have opted in to a particular topic. You compose topic messages as
needed, andFCMhandles routing and delivering the message reliably to the right
devices.
For example, users of a local tide
forecasting app could opt in to a "tidal currents alerts" topic and receive
notifications of optimal saltwater fishing conditions in specified areas. Users of a sports app
could subscribe to automatic updates in live game scores for their favorite
teams.
Some things to keep in mind about topics:
Topic messaging is best suited for content such as weather, or other publicly
available information.
Topic messages areoptimized for throughput rather than latency. For fast, secure delivery to
single devices or small groups of devices,target messages to registration tokens,
not topics.
If you need to send messages to multiple devicesper user, considerdevice group messagingfor those use cases.
Topic messaging supports unlimited subscriptions for each topic. However,FCMenforces limits in these areas:
One app instance can be subscribed to no more than 2000 topics.
If you are usingbatch importto subscribe app instances, each request is limited to 1000 app instances.
The frequency of new subscriptions is rate-limited per project. If you send too many
subscription requests in a short period of time,FCMservers will respond with a429 RESOURCE_EXHAUSTED("quota exceeded") response. Retry with
exponential backoff.
Subscribe the client app to a topic
Client apps can subscribe to any existing topic, or they can create a new
topic. When a client app subscribes to a new topic name (one that does
not already exist for your Firebase project), a new topic of that name is
created inFCMand any client can subsequently subscribe to it.
To subscribe to a topic, the client app callsFirebase Cloud MessagingsubscribeToTopic()with theFCMtopic name. This method
returns aTask, which can be used by a completion listener to determine whether
the subscription succeeded:
To unsubscribe, the client app callsFirebase Cloud MessagingunsubscribeFromTopic()with the topic name.
Manage topic subscriptions on the server
TheFirebaseAdmin SDKallows you to perform basic
topic management tasks from the server side. Given their registration
token(s), you can subscribe and unsubscribe client app instances in bulk using
server logic.
You can subscribe client app instances to any existing topic, or
you can create a new topic. When you use the API to subscribe a client app
to a new topic (one that does not already exist for your Firebase project),
a new topic of that name is created in FCM and any client can subsequently
subscribe to it.
You can pass a list of registration tokens to theFirebaseAdmin SDKsubscription method to subscribe the corresponding devices to a topic:
Node.js
// These registration tokens come from the client FCM SDKs.constregistrationTokens=['YOUR_REGISTRATION_TOKEN_1',// ...'YOUR_REGISTRATION_TOKEN_n'];// Subscribe the devices corresponding to the registration tokens to the// topic.getMessaging().subscribeToTopic(registrationTokens,topic).then((response)=>{// See the MessagingTopicManagementResponse reference documentation// for the contents of response.console.log('Successfully subscribed to topic:',response);}).catch((error)=>{console.log('Error subscribing to topic:',error);});
Java
// These registration tokens come from the client FCM SDKs.List<String>registrationTokens=Arrays.asList("YOUR_REGISTRATION_TOKEN_1",// ..."YOUR_REGISTRATION_TOKEN_n");// Subscribe the devices corresponding to the registration tokens to the// topic.TopicManagementResponseresponse=FirebaseMessaging.getInstance().subscribeToTopic(registrationTokens,topic);// See the TopicManagementResponse reference documentation// for the contents of response.System.out.println(response.getSuccessCount()+" tokens were subscribed successfully");
Python
# These registration tokens come from the client FCM SDKs.registration_tokens=['YOUR_REGISTRATION_TOKEN_1',# ...'YOUR_REGISTRATION_TOKEN_n',]# Subscribe the devices corresponding to the registration tokens to the# topic.response=messaging.subscribe_to_topic(registration_tokens,topic)# See the TopicManagementResponse reference documentation# for the contents of response.print(response.success_count,'tokens were subscribed successfully')
Go
// These registration tokens come from the client FCM SDKs.registrationTokens:=[]string{"YOUR_REGISTRATION_TOKEN_1",// ..."YOUR_REGISTRATION_TOKEN_n",}// Subscribe the devices corresponding to the registration tokens to the// topic.response,err:=client.SubscribeToTopic(ctx,registrationTokens,topic)iferr!=nil{log.Fatalln(err)}// See the TopicManagementResponse reference documentation// for the contents of response.fmt.Println(response.SuccessCount,"tokens were subscribed successfully")
C#
// These registration tokens come from the client FCM SDKs.varregistrationTokens=newList<string>(){"YOUR_REGISTRATION_TOKEN_1",// ..."YOUR_REGISTRATION_TOKEN_n",};// Subscribe the devices corresponding to the registration tokens to the// topicvarresponse=awaitFirebaseMessaging.DefaultInstance.SubscribeToTopicAsync(registrationTokens,topic);// See the TopicManagementResponse reference documentation// for the contents of response.Console.WriteLine($"{response.SuccessCount} tokens were subscribed successfully");
The AdminFCMAPI also allows you to unsubscribe devices from a topic
by passing registration tokens to the appropriate
method:
Node.js
// These registration tokens come from the client FCM SDKs.constregistrationTokens=['YOUR_REGISTRATION_TOKEN_1',// ...'YOUR_REGISTRATION_TOKEN_n'];// Unsubscribe the devices corresponding to the registration tokens from// the topic.getMessaging().unsubscribeFromTopic(registrationTokens,topic).then((response)=>{// See the MessagingTopicManagementResponse reference documentation// for the contents of response.console.log('Successfully unsubscribed from topic:',response);}).catch((error)=>{console.log('Error unsubscribing from topic:',error);});
Java
// These registration tokens come from the client FCM SDKs.List<String>registrationTokens=Arrays.asList("YOUR_REGISTRATION_TOKEN_1",// ..."YOUR_REGISTRATION_TOKEN_n");// Unsubscribe the devices corresponding to the registration tokens from// the topic.TopicManagementResponseresponse=FirebaseMessaging.getInstance().unsubscribeFromTopic(registrationTokens,topic);// See the TopicManagementResponse reference documentation// for the contents of response.System.out.println(response.getSuccessCount()+" tokens were unsubscribed successfully");
Python
# These registration tokens come from the client FCM SDKs.registration_tokens=['YOUR_REGISTRATION_TOKEN_1',# ...'YOUR_REGISTRATION_TOKEN_n',]# Unubscribe the devices corresponding to the registration tokens from the# topic.response=messaging.unsubscribe_from_topic(registration_tokens,topic)# See the TopicManagementResponse reference documentation# for the contents of response.print(response.success_count,'tokens were unsubscribed successfully')
Go
// These registration tokens come from the client FCM SDKs.registrationTokens:=[]string{"YOUR_REGISTRATION_TOKEN_1",// ..."YOUR_REGISTRATION_TOKEN_n",}// Unsubscribe the devices corresponding to the registration tokens from// the topic.response,err:=client.UnsubscribeFromTopic(ctx,registrationTokens,topic)iferr!=nil{log.Fatalln(err)}// See the TopicManagementResponse reference documentation// for the contents of response.fmt.Println(response.SuccessCount,"tokens were unsubscribed successfully")
C#
// These registration tokens come from the client FCM SDKs.varregistrationTokens=newList<string>(){"YOUR_REGISTRATION_TOKEN_1",// ..."YOUR_REGISTRATION_TOKEN_n",};// Unsubscribe the devices corresponding to the registration tokens from the// topicvarresponse=awaitFirebaseMessaging.DefaultInstance.UnsubscribeFromTopicAsync(registrationTokens,topic);// See the TopicManagementResponse reference documentation// for the contents of response.Console.WriteLine($"{response.SuccessCount} tokens were unsubscribed successfully");
ThesubscribeToTopic()andunsubscribeFromTopic()methods results in an
object containing the response fromFCM. The return type has the same
format regardless of the number of registration tokens specified in the
request.
In case of an error (authentication failures, invalid token or topic etc.)
these methods result in an error.
For a full list of error codes, including descriptions
and resolution steps, seeAdminFCMAPI Errors.
Receive and handle topic messages
FCMdelivers topic messages in the same way as other downstream
messages.
To receive messages, use a service that extendsFirebaseMessagingService.
Your service should override theonMessageReceivedandonDeletedMessagescallbacks.
onMessageReceivedis provided for most message types, with the following
exceptions:
Notification messages delivered when your app is in the background. In this
case, the notification is delivered to the device's system tray. A user tap on a notification
opens the app launcher by default.
Messages with both notification and data payload, when received in the background.
In this case, the notification is delivered to the device's system tray,
and the data payload is delivered in the extras of the
intent of your launcher Activity.
In summary:
App state
Notification
Data
Both
Foreground
onMessageReceived
onMessageReceived
onMessageReceived
Background
System tray
onMessageReceived
Notification: system tray Data: in extras of the intent.
TheonMessageReceivedcallback is given timeouts that enable you to simply post a
notification but the timers are not designed to allow the app to access the network or to do
additional work. As such, if your app does anything more complicated, you need to do additional
work to ensure the app can complete its work.
If you expect your app could require close to 10 seconds to handle a message, you shouldschedule a WorkManager jobor follow theWakeLock guidance below. In some
cases, the time window for handling a message may be shorter than 10 seconds depending on delays
incurred ahead of callingonMessageReceived, including OS delays, app startup time,
the main thread being blocked by other operations, or previousonMessageReceivedcalls taking too long. After that timer expires, your app may be
subject toprocess killingorbackground execution limits. Keep in mind, latencies for network transactions and app startup can be significant, so when in
doubt, plan for your message processing to run long if there are any asynchronous dependencies
such as network access or intensive data loading requirements.
Edit the app manifest
To useFirebaseMessagingService, you need to add the following in your
app manifest:
Also, you're recommended to set default values to customize the appearance of notifications. You
can specify a custom default icon and a custom default color that are applied whenever
equivalent values are not set in the notification payload.
Add these lines inside theapplicationtag to set the custom default icon and custom color:
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages.SeeREADME(https://goo.gl/l4GJaQ)formore.-->
<meta-dataandroid:name="com.google.firebase.messaging.default_notification_icon"android:resource="@drawable/ic_stat_ic_notification"/>
<!-- Set color used with incoming notification messages. This is used when no color is set for the incomingnotificationmessage.SeeREADME(https://goo.gl/6BKBk7)formore.-->
<meta-dataandroid:name="com.google.firebase.messaging.default_notification_color"android:resource="@color/colorAccent"/>
Any notification message that does not explicitly set the color in the notification
payload.
If no custom default icon is set and no icon is set in the notification payload,
Android displays the application icon rendered in white.
OverrideonMessageReceived
By overriding the methodFirebaseMessagingService.onMessageReceived,
you can perform actions based on the receivedRemoteMessageobject and get the message data:
Kotlin
overridefunonMessageReceived(remoteMessage:RemoteMessage){// TODO(developer): Handle FCM messages here.// Not getting messages here? See why this may be: https://goo.gl/39bRNJLog.d(TAG,"From:${remoteMessage.from}")// Check if message contains a data payload.if(remoteMessage.data.isNotEmpty()){Log.d(TAG,"Message data payload:${remoteMessage.data}")// Check if data needs to be processed by long running jobif(needsToBeScheduled()){// For long-running tasks (10 seconds or more) use WorkManager.scheduleJob()}else{// Handle message within 10 secondshandleNow()}}// Check if message contains a notification payload.remoteMessage.notification?.let{Log.d(TAG,"Message Notification Body:${it.body}")}// Also if you intend on generating your own notifications as a result of a received FCM// message, here is where that should be initiated. See sendNotification method below.}
@OverridepublicvoidonMessageReceived(RemoteMessageremoteMessage){// TODO(developer): Handle FCM messages here.// Not getting messages here? See why this may be: https://goo.gl/39bRNJLog.d(TAG,"From: "+remoteMessage.getFrom());// Check if message contains a data payload.if(remoteMessage.getData().size()>0){Log.d(TAG,"Message data payload: "+remoteMessage.getData());if(/* Check if data needs to be processed by long running job */true){// For long-running tasks (10 seconds or more) use WorkManager.scheduleJob();}else{// Handle message within 10 secondshandleNow();}}// Check if message contains a notification payload.if(remoteMessage.getNotification()!=null){Log.d(TAG,"Message Notification Body: "+remoteMessage.getNotification().getBody());}// Also if you intend on generating your own notifications as a result of a received FCM// message, here is where that should be initiated. See sendNotification method below.}
If your app needs to keep the device awake while processing an FCM message, then it will need to
hold a WakeLock during this time or it will need to create a WorkManager job. WakeLocks work well
for short processing activities that might exceed theonMessageReceiveddefault
timeouts. For extended workflows, such as sending multiple serial RPCs to your servers, using a
WorkManager job is more appropriate than a WakeLock. In this section we focus on how to use
WakeLocks. A WakeLock prevents the device from sleeping while your app is running, which can
result in increased battery use, so use of WakeLocks should be reserved for cases where your app
should not be paused while handling the message such as:
Notifications to the user that are time sensitive.
Interactions with something off device that shouldn't be interrupted (such as network transfers
or communications with another device, like a paired watch).
First you'll need to make sure that your app requests the WakeLock permission (the FCM SDK
includes this by default, so normally nothing needs to be added).
Then your app will need to acquire a WakeLock at the start of theFirebaseMessagingService.onMessageReceived()callback and release it at the end of the callback.
App's customFirebaseMessagingService:
@Override
public void onMessageReceived(final RemoteMessage message) {
// If this is a message that is time sensitive or shouldn't be interrupted
WakeLock wakeLock = getSystemService(PowerManager.class).newWakeLock(PARTIAL_WAKE_LOCK, "myApp:messageReceived");
try {
wakeLock.acquire(TIMEOUT_MS);
// handle message
...
finally {
wakeLock.release();
}
}
OverrideonDeletedMessages
In some situations,FCMmay not deliver a message. This occurs when there are too many
messages (>100) pending for
your app on a particular device at the time it connects or if the device hasn't connected toFCMin more than one month. In these cases,
you may receive a callback toFirebaseMessagingService.onDeletedMessages()When the app instance receives this callback,
it should perform a full sync with your app server. If you haven't sent a message to the app on that
device within the last 4 weeks,FCMwon't callonDeletedMessages().
Handle notification messages in a backgrounded app
When your app is in the background, Android directs notification messages to
the system tray. A user tap on the notification opens the app launcher by
default.
This includes messages that contain both notification and data
payload (and all messages sent from the Notifications console).
In these cases, the notification is delivered to the device's
system tray, and the data payload is delivered in the extras of the intent
of your launcher Activity.
For insight into message delivery to your app, see
theFCMreporting dashboard, which records the
number of messages sent and opened on Apple and Android devices, along with
data for "impressions" (notifications seen by users) for Android apps.
Build send requests
After you have created a topic, either by subscribing client app instances to
the topic on the client side or via theserver API, you can send messages to the
topic. If this is your first time building send requests forFCM,
see the guide toyour server environment andFCMfor
important background and setup information.
In your sending logic on the backend, specify the desired topic name
as shown:
Node.js
// The topic name can be optionally prefixed with "/topics/".consttopic='highScores';constmessage={data:{score:'850',time:'2:45'},topic:topic};// Send a message to devices subscribed to the provided topic.getMessaging().send(message).then((response)=>{// Response is a message ID string.console.log('Successfully sent message:',response);}).catch((error)=>{console.log('Error sending message:',error);});
Java
// The topic name can be optionally prefixed with "/topics/".Stringtopic="highScores";// See documentation on defining a message payload.Messagemessage=Message.builder().putData("score","850").putData("time","2:45").setTopic(topic).build();// Send a message to the devices subscribed to the provided topic.Stringresponse=FirebaseMessaging.getInstance().send(message);// Response is a message ID string.System.out.println("Successfully sent message: "+response);
# The topic name can be optionally prefixed with "/topics/".topic='highScores'# See documentation on defining a message payload.message=messaging.Message(data={'score':'850','time':'2:45',},topic=topic,)# Send a message to the devices subscribed to the provided topic.response=messaging.send(message)# Response is a message ID string.print('Successfully sent message:',response)
// The topic name can be optionally prefixed with "/topics/".topic:="highScores"// See documentation on defining a message payload.message:=&messaging.Message{Data:map[string]string{"score":"850","time":"2:45",},Topic:topic,}// Send a message to the devices subscribed to the provided topic.response,err:=client.Send(ctx,message)iferr!=nil{log.Fatalln(err)}// Response is a message ID string.fmt.Println("Successfully sent message:",response)
// The topic name can be optionally prefixed with "/topics/".vartopic="highScores";// See documentation on defining a message payload.varmessage=newMessage(){Data=newDictionary<string,string>(){{"score","850"},{"time","2:45"},},Topic=topic,};// Send a message to the devices subscribed to the provided topic.stringresponse=awaitFirebaseMessaging.DefaultInstance.SendAsync(message);// Response is a message ID string.Console.WriteLine("Successfully sent message: "+response);
To send a message to acombinationof topics,
specify acondition, which is a boolean expression that specifies the
target topics. For example, the following condition will send messages to
devices that are subscribed toTopicAand eitherTopicBorTopicC:
"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
FCMfirst evaluates any conditions in parentheses, and then evaluates
the expression from left to right. In the above expression, a user subscribed to
any single topic does not receive the message. Likewise, a user who does not
subscribe toTopicAdoes not receive the message. These combinations do
receive it:
TopicAandTopicB
TopicAandTopicC
You can include up to five topics in your conditional expression.
To send to a condition:
Node.js
// Define a condition which will send to devices which are subscribed// to either the Google stock or the tech industry topics.constcondition='\'stock-GOOG\' in topics || \'industry-tech\' in topics';// See documentation on defining a message payload.constmessage={notification:{title:'$FooCorp up 1.43% on the day',body:'$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'},condition:condition};// Send a message to devices subscribed to the combination of topics// specified by the provided condition.getMessaging().send(message).then((response)=>{// Response is a message ID string.console.log('Successfully sent message:',response);}).catch((error)=>{console.log('Error sending message:',error);});
Java
// Define a condition which will send to devices which are subscribed// to either the Google stock or the tech industry topics.Stringcondition="'stock-GOOG' in topics || 'industry-tech' in topics";// See documentation on defining a message payload.Messagemessage=Message.builder().setNotification(Notification.builder().setTitle("$GOOG up 1.43% on the day").setBody("$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.").build()).setCondition(condition).build();// Send a message to devices subscribed to the combination of topics// specified by the provided condition.Stringresponse=FirebaseMessaging.getInstance().send(message);// Response is a message ID string.System.out.println("Successfully sent message: "+response);
# Define a condition which will send to devices which are subscribed# to either the Google stock or the tech industry topics.condition="'stock-GOOG' in topics || 'industry-tech' in topics"# See documentation on defining a message payload.message=messaging.Message(notification=messaging.Notification(title='$GOOG up 1.43% on the day',body='$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.',),condition=condition,)# Send a message to devices subscribed to the combination of topics# specified by the provided condition.response=messaging.send(message)# Response is a message ID string.print('Successfully sent message:',response)
// Define a condition which will send to devices which are subscribed// to either the Google stock or the tech industry topics.condition:="'stock-GOOG' in topics || 'industry-tech' in topics"// See documentation on defining a message payload.message:=&messaging.Message{Data:map[string]string{"score":"850","time":"2:45",},Condition:condition,}// Send a message to devices subscribed to the combination of topics// specified by the provided condition.response,err:=client.Send(ctx,message)iferr!=nil{log.Fatalln(err)}// Response is a message ID string.fmt.Println("Successfully sent message:",response)
// Define a condition which will send to devices which are subscribed// to either the Google stock or the tech industry topics.varcondition="'stock-GOOG' in topics || 'industry-tech' in topics";// See documentation on defining a message payload.varmessage=newMessage(){Notification=newNotification(){Title="$GOOG up 1.43% on the day",Body="$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.",},Condition=condition,};// Send a message to devices subscribed to the combination of topics// specified by the provided condition.stringresponse=awaitFirebaseMessaging.DefaultInstance.SendAsync(message);// Response is a message ID string.Console.WriteLine("Successfully sent message: "+response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"condition": "'dogs' in topics || 'cats' in topics",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message",
}
}
}
cURL command:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"notification": {
"title": "FCM Message",
"body": "This is a Firebase Cloud Messaging Topic Message!",
},
"condition": "'dogs' in topics || 'cats' in topics"
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-09-05 UTC."],[],[],null,[]]