FCM (Push)
Firebase Cloud Messaging (FCM) sends push notifications to iOS, Android, web, and desktop from one API. Combined with the Admin SDK or HTTP v1 API server-side, you get topic broadcasts, device targeting, scheduling — without managing your own APNs/Web Push infrastructure.
Token, send, topics, payloads
EXAMPLE
// 1) Setup — server side
// npm install firebase-admin
import { initializeApp, applicationDefault } from 'firebase-admin/app';
import { getMessaging } from 'firebase-admin/messaging';
initializeApp({ credential: applicationDefault() });
const messaging = getMessaging();
// 2) Client gets FCM token (Web example)
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
const app = initializeApp({ /* config */ });
const messaging = getMessaging(app);
// Request permission + token
async function registerForPush() {
const permission = await Notification.requestPermission();
if (permission !== 'granted') return null;
const token = await getToken(messaging, {
vapidKey: 'BNkR...',
serviceWorkerRegistration: await navigator.serviceWorker.register('/firebase-messaging-sw.js'),
});
// Send token to your backend
await fetch('/api/devices', { method: 'POST', body: JSON.stringify({ token }) });
return token;
}
// firebase-messaging-sw.js (service worker for background messages)
importScripts('https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js');
firebase.initializeApp({ /* same config */ });
const sw_messaging = firebase.messaging();
sw_messaging.onBackgroundMessage((payload) => {
self.registration.showNotification(payload.notification.title, {
body: payload.notification.body,
icon: '/icon-192.png',
data: payload.data,
});
});
// 3) Foreground messages — handled in main thread
onMessage(messaging, (payload) => {
console.log('foreground push', payload);
// Show in-app banner or react to the data
});
// 4) Send to a single token (server)
await messaging.send({
token: 'fcm-token-here',
notification: { title: 'Hello', body: 'World' },
data: { route: '/dashboard', userId: '42' }, // strings only
android: {
notification: { channelId: 'default', priority: 'high', sound: 'default' },
},
apns: {
payload: { aps: { sound: 'default', badge: 1, 'mutable-content': 1 } },
},
webpush: {
notification: { icon: '/icon-192.png' },
fcmOptions: { link: 'https://app.example.com/dashboard' },
},
});
// 5) Batch send — up to 500 messages
const messages = tokens.map((token) => ({
token,
notification: { title: 'Sale!', body: '24h discount inside' },
}));
const response = await messaging.sendEach(messages);
console.log(`${response.successCount} sent, ${response.failureCount} failed`);
response.responses.forEach((r, i) => {
if (!r.success && r.error?.code === 'messaging/registration-token-not-registered') {
// Token invalid — remove from your DB
deleteToken(tokens[i]);
}
});
// 6) Topic broadcasts — millions of devices
await messaging.subscribeToTopic(['token1', 'token2'], 'news');
await messaging.send({
topic: 'news',
notification: { title: 'Breaking', body: 'Big news' },
});
await messaging.unsubscribeFromTopic(['token1'], 'news');
// Or send to a CONDITION (combine topics):
await messaging.send({
condition: "'news' in topics && 'finance' in topics",
notification: { title: 'Markets update' },
});
// 7) Per-platform overrides
await messaging.send({
token: 'fcm-token',
notification: { title: 'Generic', body: 'Default' },
android: {
priority: 'high',
notification: {
title: 'Android title', // overrides generic
color: '#ff5722',
sound: 'default',
visibility: 'public',
channelId: 'high_priority',
},
},
apns: {
headers: { 'apns-priority': '10' },
payload: {
aps: {
alert: { title: 'iOS title', body: 'iOS body' },
'thread-id': 'group-1',
'mutable-content': 1,
'content-available': 1,
},
},
},
});
// 8) Data-only messages — silent push for background sync
await messaging.send({
token: 'fcm-token',
data: { type: 'sync', orderId: '42' },
android: { priority: 'high' },
apns: { payload: { aps: { 'content-available': 1 } } },
});
// • Android: app's FCM listener fires; can update local DB
// • iOS: app gets background time (limited by system)
// 9) Notification channels (Android 8+)
// Define in app at install/runtime:
await LocalNotifications.createChannel({
id: 'high_priority',
name: 'Important alerts',
importance: 5,
visibility: 1,
});
// 10) Token lifecycle
// • Tokens rotate; client onTokenRefresh / getToken on each login + 30 days
// • Save (userId, token, platform, lastSeen) to your DB
// • Delete tokens flagged as 'unregistered' on send errors
// • On logout: delete the token + revoke server-side
// 11) Scheduled / delayed sends
// FCM doesn't schedule natively. Schedule on your backend:
import { CronJob } from 'cron';
new CronJob('0 9 * * 1', () => sendNewsletter(), null, true);
// Or use Cloud Functions scheduled triggers.
// 12) Click handlers + deep links
// Include 'data.route' + 'data.target' + 'webpush.fcmOptions.link' so the app can navigate.
// 13) Cost + limits
// • FCM is FREE for typical use
// • Topic msgs: 1 message rate per second per topic
// • Multicast: 500 tokens per send call
// • Daily quotas vary; check Firebase console
// 14) iOS specific — APNs certificate
// Firebase Console → Project Settings → Cloud Messaging → upload .p8 APNs auth key
// Or .p12 certificate (legacy)
// Without this, iOS pushes fail silently.
// 15) Testing
// Firebase Console → Cloud Messaging → 'New campaign'
// Or curl HTTP v1:
curl -X POST 'https://fcm.googleapis.com/v1/projects/$PROJECT/messages:send' \\
-H 'Authorization: Bearer $ACCESS_TOKEN' \\
-H 'Content-Type: application/json' \\
-d '{ "message": { "token": "...", "notification": { "title": "Hi" } } }'
// 16) Common bugs
// • iOS doesn't show notifications → APNs key missing in Firebase Console
// • Tokens save but pushes don't arrive → check Service Worker scope; HTTPS required
// • 'Requested entity was not found' → token expired; remove from DB
// • OPPO/Xiaomi background killing app → vendor power management; show user guidance
// • Data-only messages on iOS without 'content-available' → not delivered
// • Web Push only on HTTPS (or localhost) — fails silently elsewhere
// • Channel id mismatch → Android shows but in 'Other' channel; create explicit channel
// • Browser closed but Service Worker not registered → background push doesn't fire
// • Topic broadcasts limited to 1/sec — implement queueing for bursts
// • Foreground messages NOT shown as system notification — show manually via Notifications API
Why it matters
FCM is the cross-platform push API: get a device token client-side, store it server-side, send via Firebase Admin SDK with per-platform overrides (android, apns, webpush). Use topics for broadcasts, data-only messages for silent sync, and delete tokens flagged registration-token-not-registered. iOS needs the APNs key uploaded to Firebase Console.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
const msg = getMessaging(app);
const token = await getToken(msg, { vapidKey: '…' });
onMessage(msg, payload => console.log('msg', payload));
Try it Yourself »
Exercise
Get the push token.
const t = await
(msg, { vapidKey: '…' });
Eight letters camelCase.
Discussion
Loading…