Push Notifications
Push notifications are the standard way to bring users back into your app. Capacitor + Firebase Cloud Messaging (FCM) gives you one cross-platform API; the configuration story differs per platform but the runtime code is shared.
FCM, plugin, permissions, payload, server
EXAMPLE
// 1) Install
// npm install @capacitor/push-notifications
// npx cap sync
import { PushNotifications, Token, PushNotificationSchema } from '@capacitor/push-notifications';
import { Capacitor } from '@capacitor/core';
// 2) Permission request + listener registration
async function registerForPush() {
if (!Capacitor.isNativePlatform()) return; // web fallback handled separately
let perm = await PushNotifications.checkPermissions();
if (perm.receive !== 'granted') {
perm = await PushNotifications.requestPermissions();
if (perm.receive !== 'granted') {
console.warn('push declined');
return;
}
}
await PushNotifications.register(); // gets token from FCM/APNs
}
// 3) Listener events
PushNotifications.addListener('registration', (token: Token) => {
// Send token to your backend
saveDeviceToken(token.value);
});
PushNotifications.addListener('registrationError', (err) => {
console.error('FCM/APNs registration failed', err);
});
PushNotifications.addListener('pushNotificationReceived', (n: PushNotificationSchema) => {
// Foreground notification — show in-app banner
console.log('foreground push', n);
});
PushNotifications.addListener('pushNotificationActionPerformed', (action) => {
// User tapped a notification
const { route } = action.notification.data;
if (route) router.navigate(route);
});
// 4) Backend — Firebase Admin SDK send
import { initializeApp } from 'firebase-admin/app';
import { getMessaging } from 'firebase-admin/messaging';
initializeApp();
async function sendToToken(token: string, title: string, body: string, data: Record<string, string>) {
return getMessaging().send({
token,
notification: { title, body },
data, // string-only key/values
android: { notification: { channelId: 'default', priority: 'high' } },
apns: {
payload: {
aps: { 'mutable-content': 1, sound: 'default', badge: 1 },
},
},
webpush: {
notification: { icon: '/icon-192.png' },
},
});
}
// 5) Topic-based broadcast
await getMessaging().subscribeToTopic([token1, token2], 'news');
await getMessaging().send({
topic: 'news',
notification: { title: 'Breaking', body: 'Big news' },
});
// 6) iOS — APNs setup (one-time)
// • Apple Developer: create an APNs key (.p8) + Team ID
// • Firebase Console → Project Settings → Cloud Messaging → upload APNs key
// • Xcode: enable 'Push Notifications' capability
// • Xcode: enable 'Background Modes' → 'Remote notifications'
// • Info.plist: no extra entry needed (handled by capability)
// 7) Android — FCM setup (one-time)
// • Firebase Console → Add Android app → download google-services.json into android/app
// • android/build.gradle: classpath 'com.google.gms:google-services:4.4.x'
// • android/app/build.gradle: apply plugin: 'com.google.gms.google-services'
// • Optional: configure default notification channel + icon resource
// AndroidManifest.xml (Capacitor 5+ creates this):
// <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id"
// android:value="default" />
// <meta-data android:name="com.google.firebase.messaging.default_notification_icon"
// android:resource="@drawable/ic_notification" />
// 8) Notification channels (Android 8+)
import { LocalNotifications } from '@capacitor/local-notifications';
await LocalNotifications.createChannel({
id: 'default',
name: 'Default',
importance: 4,
visibility: 1,
sound: 'default',
});
// 9) Foreground vs background
// • Foreground: pushNotificationReceived fires; you decide whether to show UI (use Local Notifications to display)
// • Background: OS shows the notification; pushNotificationActionPerformed fires on tap
// • Killed: same as background; payload includes 'notification' field — OS handles display
// 10) Web push
// Web push uses a service worker + VAPID keys.
// • Generate VAPID via firebase-admin
// • Register service worker, subscribe to PushManager
// • Forward subscription to your backend
// Capacitor's plugin doesn't cover web; use the Firebase JS SDK directly:
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
const token = await getToken(messaging, { vapidKey: 'BNkR...' });
onMessage(messaging, (payload) => {
new Notification(payload.notification.title, { body: payload.notification.body });
});
// 11) Payload size + deep links
// • Max payload ≈ 4 KB. Keep extra data minimal.
// • For deep linking, set 'data.route' or 'data.deep_link' on the payload; parse in pushNotificationActionPerformed.
// 12) Server tokens — manage carefully
// • Tokens rotate; backend MUST persist by user + device id and REPLACE on registration callbacks
// • Remove tokens that come back as 'registration-token-not-registered' from FCM
// • Don't ship FCM server keys in client code — keep on backend only
// 13) UX patterns
// • Don't ask for permission immediately on first launch — explain why first (rationale screen)
// • Provide a Settings toggle to opt out without app permissions UI
// • Group related notifications via 'tag' / 'collapse_key'
// • Quiet hours — respect time zone
// • Localise the payload server-side
// 14) Testing
// • Use Firebase Console → Cloud Messaging → 'New campaign' for ad-hoc sends
// • Or curl directly:
curl -X POST https://fcm.googleapis.com/v1/projects/PROJECT/messages:send \\
-H "Authorization: Bearer $ACCESS" \\
-H "Content-Type: application/json" \\
-d '{"message":{"token":"...","notification":{"title":"Hi","body":"World"}}}'
// 15) Common bugs
// • Missing APNs key → iOS device registers but no notifications received
// • App killed but notifications missing → OS battery optimisations; vendor-specific (especially Xiaomi/OPPO)
// • Token saved but stale → user reinstalls; old token still in DB; clean on send failure
// • Notification icon on Android shows as a white square → drawable not configured / Android 5+ requires monochrome
// • Asking permission too early → users decline; explain first
// • Background data-only messages on iOS need 'content-available': 1 — and even then are throttled
// • Topic subscription cache stuck → call unsubscribeFromTopic + resubscribe
// • Web Service Worker not registered → PushManager fails silently in the console
Why it matters
Capacitor + Firebase Cloud Messaging is the standard push stack on Ionic. Request permission with a rationale screen first, send the device token to your backend on the registration event, handle taps via pushNotificationActionPerformed, and configure APNs (iOS) + google-services.json (Android) once. Keep server keys on the server.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { PushNotifications } from '@capacitor/push-notifications';
await PushNotifications.requestPermissions();
await PushNotifications.register();
PushNotifications.addListener('registration', t => console.log(t.value));
Try it Yourself »
Discussion
Loading…