FCM Flow
Firbase Setup for FCM
Section titled “Firbase Setup for FCM”- Add firebase to your android project.
- Create a firebase project
- Register your app with Firebase
-
Go to the Firebase console.
- In the center of the project overview page, click the Android icon or Add app to launch the setup workflow.
- Enter your app’s package name (com.ankurplus.nursery) in the Android package name field.
Make sure to enter the package name that your app is actually using. The package name value is case-sensitive, and it cannot be changed for this Firebase Android app after it’s registered with your Firebase project.
- (Optional) Enter an App nickname, which is an internal, convenience identifier that is only visible to you in the Firebase console.
- Click Register app.
-
Add a firebase configuration file
- Download the
google-services.jsonfile from the Firebase console. - Place the file in the app module’s directory. (Cordova project’s root directory)
- Download the
-
For cordova - Use
config.xmland cordova-plugin-firebasex to set up Firebase using google-services.json file.
Token Registration Flow
Section titled “Token Registration Flow”Cordova → Node.js
Section titled “Cordova → Node.js”- Endpoint:
POST /register-token - Payload:
{ "token": "AAA.BBB.CCC"}Node.js validates and stores tokens in the database. Respond with:
{ "success": true}Cordova → Node.js
Section titled “Cordova → Node.js”- Endpoint:
POST /send-global-notification - Payload:
{ "title": "New Offer", "body": "Flat 50% off today!", "imageUrl": "https://your-server.com/banners/offer50.png"}- Response:
{ "success": true}Node.js → Firebase
Section titled “Node.js → Firebase”const message = { notification: { title: req.body.title, body: req.body.body, image: req.body.imageUrl, }, tokens: allTokensFromDB,};
// firebase sdk methodsconst result = await admin.messaging().sendEachForMulticast(message);- Android automatically displays title + body + big image (if provided) when the notification payload is used.
- We cannot style this in cordova - Because Android OS shows the notification before your app gets control.
Alternative: Node Sends Data Only - We can style as we like
Section titled “Alternative: Node Sends Data Only - We can style as we like”When you need Cordova to fully control the notification UI (dynamic images, custom layouts):
Node → Firebase (data payload)
Section titled “Node → Firebase (data payload)”const message = { token, data: { title: "New Offer", body: "Flat 50% off today!", type: "OFFER", },};Cordova handler
Section titled “Cordova handler”document.addEventListener( "deviceready", function () { console.log("Device is ready");
// Listen for FCM messages FirebasePlugin.onMessageReceived( function (msg) { console.log("Message received from FCM:", msg);
// 1. Normalize data // - For data-only messages, `msg.data` will have what backend sent var data = msg.data || msg || {};
var title = data.title || "Notification"; var body = data.body || "";
// 2. Decide styling based on data from backend // Example: choose emoji & default image based on type var emoji = ""; var defaultImage = null;
switch (data.type) { case "SALE": emoji = " 🛍️"; defaultImage = "https://your-server.com/banners/default-sale.png"; break; case "ALERT": emoji = " ⚠️"; defaultImage = "https://your-server.com/banners/alert.png"; break; default: emoji = ""; defaultImage = null; }
// Prefer backend imageUrl, else fallback to default based on type var bigImageUrl = data.imageUrl || defaultImage;
// Accent color (from backend or fallback) var color = data.color || "#4CAF50";
// 3. Build local notification options var notificationOptions = { id: Date.now(), // unique id title: title + emoji, // add emoji here body: body, smallIcon: "notification_icon", // must exist in your resources iconColor: color, vibration: true, sound: "default", foreground: true, // show even if app is in foreground };
// If you want big picture (banner / map snapshot style) if (bigImageUrl) { notificationOptions.bigPicture = bigImageUrl; // Firebasex supports this }
// 4. Show styled local notification FirebasePlugin.scheduleLocalNotification( notificationOptions, function () { console.log("Local notification shown with styling"); }, function (err) { console.error("Error showing local notification", err); }, ); }, function (error) { console.error("Error in onMessageReceived", error); }, ); }, false,);This approach lets the device decide which artwork, sounds, or actions to attach.
Payload Reference
Section titled “Payload Reference”FCM distinguishes two payload types:
Notification Payload (system UI)
Section titled “Notification Payload (system UI)”"notification": { "title": "Order shipped", "body": "Your order #123 is on the way!", "image": "https://example.com/order_map.png", "android_channel_id": "my_channel", "icon": "stock_ticker_update", "color": "#4CAF50", "tag": "group_tag", "click_action": "OPEN_SCREEN", "sound": "default", "title_loc_key": "TITLE_STRING", "body_loc_key": "BODY_STRING", "body_loc_args": ["arg1"]}Field names such as title and body are fixed; renaming them prevents Android from rendering the system notification automatically.
Data Payload (custom key/value)
Section titled “Data Payload (custom key/value)”"data": { "type": "ORDER_UPDATE", "orderId": "123", "priority": "high", "imageUrl": "https://example.com/promo.png", "foo": "bar", "whatever": "you want"}- Accepts any string key/value pairs.
- Cordova receives values exactly as sent.
- Use it to drive custom logic (deep links, themes, in-app banners).
Full Message Example
Section titled “Full Message Example”{ "message": { "token": "DEVICE_TOKEN", "notification": { "title": "Order shipped", "body": "Your order #123 is on the way!", "image": "https://example.com/order_map.png", "sound": "default", "color": "#4CAF50" }, "data": { "orderId": "123", "type": "ORDER_UPDATE", "banner": "https://example.com/banner.png", "screen": "order_details", "custom1": "value1", "custom2": "value2" }, "android": { "priority": "HIGH" } }}Key Takeaways
Section titled “Key Takeaways”- Use notification payload for immediate system UI (requires
title+body). - Use data payload for unlimited custom fields and local notification rendering.
- Cordova can mix both: show system notification when app is backgrounded, and raise a customized local notification when foregrounded.
- Always keep
google-services.json, Gradle plugins, and Firebase BoM versions in sync across environments.
Is it necessary to store tokens in a database?
Section titled “Is it necessary to store tokens in a database?”Storing tokens in a database is only required when you need to target specific devices or user segments. Decide based on the type of notification:
| Use case | Store tokens? | Recommended approach |
|---|---|---|
| Global broadcast (same message to all) | No | FCM Topics |
| User/device-specific alerts | Yes | Token database |
| Segmented audiences (VIP, region) | Often | Token DB + metadata or multiple topics |
When a DB is not needed
Section titled “When a DB is not needed”If you only send global announcements (offers, maintenance windows, feature launches), skip token storage and rely on topics:
// CordovaFirebasePlugin.subscribe("global_notifications");
await admin.messaging().send({ topic: "global_notifications", notification: { title: "New Offer", body: "Big discounts today!", },});Every device subscribed to global_notifications receives the message.
- No additional DB storage on your side. When a device calls FirebasePlugin.subscribe(“global_notifications”), FCM records internally that the token belongs to that topic. Firebase handles that mapping; your backend just targets the topic name.
When a DB is needed
Section titled “When a DB is needed”For per-user updates (orders, rides, reminders) the backend must know which token belongs to whom:
- Cordova posts the token to the backend.
- Backend stores the token (optionally linked to user/device metadata).
- Backend queries the token(s) and calls
sendEachForMulticastorsendToDevice.
Without persistence the backend cannot target individual recipients.
Handling Taps & Call-To-Actions
Section titled “Handling Taps & Call-To-Actions”Tell the app “where to go”
Section titled “Tell the app “where to go””Create a backend/app contract: include navigation hints in the payload so the client can open the right screen.
{ "message": { "topic": "global", "data": { "title": "Order placed", "body": "Order #123 has been created", "screen": "order_details", "orderId": "123" } }}On Cordova, centralize the routing logic:
function handleNotificationNavigation(data) { switch (data.screen) { case "order_details": openOrderDetails(data.orderId); break; case "offers": openOffersPage(); break; default: openHome(); break; }}Detecting taps with cordova-plugin-firebasex
Section titled “Detecting taps with cordova-plugin-firebasex”FirebasePlugin.onMessageReceived provides a tap flag:
tap: "background"→ user tapped while app was backgrounded or closed.tap: "foreground"→ user tapped a foreground notification.tapundefined → message just arrived; no tap yet.
document.addEventListener( "deviceready", function () { FirebasePlugin.onMessageReceived( function (message) { const data = message.data || {}; const tap = message.tap;
if (tap) { handleNotificationNavigation(data); return; }
// Message received while app is active; decide whether to show a local notification or banner. }, function (err) { console.error("onMessageReceived error:", err); } ); }, false);Adding call-to-action buttons
Section titled “Adding call-to-action buttons”Android’s native FCM notification payload has limited support for multiple actions. For richer CTAs, send a data-only push and render your own local notification using cordova-plugin-local-notifications (or similar):
cordova.plugins.notification.local.schedule({ id: Date.now(), title: data.title, text: data.body, actions: [ { id: "VIEW_ORDER", title: "View" }, { id: "CANCEL_ORDER", title: "Cancel" }, ], data: { screen: "order_details", orderId: data.orderId, },});Handle the action callback to inspect actionId and data, then call handleNotificationNavigation or trigger other flows (cancel order, mark read, etc.).
Start with simple tap-to-open flows; layer CTAs later when you need richer interactions.