iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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: '…' });

Discussion

Loading…