Skip to content

capacitor-push-notifications

Sets up and debugs remote push in Capacitor apps with @capacitor/push-notifications (FCM on Android, APNs on iOS), or Capgo alternatives @capgo/capacitor-firebase-messaging and @capgo/capacitor-notifications. Covers APNs keys, Push Notifications capability, AppDelegate `didRegisterForRemoteNotificationsWithDeviceToken` forwarding (stays in AppDelegate under the Capacitor 8.5 UIScene lifecycle), Android 13+ POST_NOTIFICATIONS permission, notification channels and icons, `firebaseMessagingVersion`, foreground `presentationOptions` (Capacitor 9 removes iOS `alert`; use `banner`/`list`), and tap routing. Use when `registration` never fires, iOS returns an APNs token instead of an FCM token, errors like "no valid aps-environment entitlement string found" appear, or notifications do not show. Do not use for local-only notifications or deep link URL handling (capacitor-deep-linking), UIScene migration (capacitor-uiscene-migration), or log capture (ios-android-logs).

1,094
installs
0 up / 0 down, score 0.00
votes
74
repo stars
Updated
less than a month ago
In the catalog since
6 Oct 2026
From
cap-go/capgo-skills
Get it through your agent npx skills add cap-go/capgo-skills@capacitor-push-notifications

Skill instructions

Rendered from the published SKILL.md, 141 lines.

Push Notifications in Capacitor

When to Use

TRIGGER when:

  • Adding remote push (FCM / APNs) to a Capacitor app
  • registration / registrationError never fire, or the token works on one platform only
  • iOS token is a 64-hex APNs token but the backend expects an FCM token
  • Notifications arrive in background but not in foreground (or vice versa)
  • Android 13+ shows no prompt and nothing is delivered
  • Upgrading to Capacitor 8.5 (UIScene) or 9 (alert presentation option removed)

Do not use:

  • Opening the app from https:// / myapp:// links: capacitor-deep-linking
  • Moving AppDelegate code to SceneDelegate: capacitor-uiscene-migration
  • Device log capture: ios-android-logs
  • Choosing other native plugins: capacitor-plugins

Choose the plugin first (ask the user if unclear)

Need Plugin
Official plugin; APNs token on iOS, FCM token on Android @capacitor/push-notifications
FCM token on both platforms, topics, Firebase-first backend @capgo/capacitor-firebase-messaging
Capgo-managed delivery (dashboard campaigns, badges, silent live-update checks, no own push server) @capgo/capacitor-notifications
Local notifications only, rich layouts @capgo/capacitor-rich-notifications or @capacitor/local-notifications

The rest of this file covers @capacitor/push-notifications. For Capgo plugins follow https://capgo.app/docs/plugins/firebase-messaging/ or https://capgo.app/docs/plugins/notifications/getting-started/.

Workflow

  1. Inspect: package.json versions (@capacitor/core, @capacitor/push-notifications), capacitor.config.* plugins.PushNotifications, ios/App/App/AppDelegate.swift, ios/App/App/*.entitlements, android/app/google-services.json, android/variables.gradle, AndroidManifest.xml.
  2. Install: npm install @capacitor/push-notifications && npx cap sync.
  3. iOS: capability + APNs key + AppDelegate forwarding. Details: references/ios-setup.md.
  4. Android: google-services.json, channel, icon, permission. Details: references/android-setup.md.
  5. JS: listeners first, then permission, then register() (below).
  6. Backend: send a test message. Payload shapes: references/sending.md.
  7. Verify with the checklist below on physical devices.

Only load a reference when its topic is in play.

JS registration (order matters)

import { PushNotifications } from '@capacitor/push-notifications';

export async function initPush() {
  // 1. Listeners before register(), or the first token is missed
  await PushNotifications.addListener('registration', ({ value }) => sendTokenToServer(value));
  await PushNotifications.addListener('registrationError', (err) => console.error('push reg', err.error));
  await PushNotifications.addListener('pushNotificationReceived', (n) => {
    // foreground only (and Android data-only while app is alive)
  });
  await PushNotifications.addListener('pushNotificationActionPerformed', ({ notification, actionId }) => {
    routeFromNotification(notification.data); // tap handling
  });

  // 2. Permission (required on iOS and Android 13+ / targetSdk 33+)
  let perm = await PushNotifications.checkPermissions();
  if (perm.receive === 'prompt' || perm.receive === 'prompt-with-rationale') {
    perm = await PushNotifications.requestPermissions();
  }
  if (perm.receive !== 'granted') return; // explain + link to settings, do not loop prompts

  // 3. Register
  await PushNotifications.register();
}

Traps:

  • Call initPush() at bootstrap so pushNotificationActionPerformed from a cold-start tap is captured.
  • Tokens rotate. Send every registration value to the server and upsert by user + device.
  • On iOS the plugin reports the raw APNs token unless AppDelegate swaps in the FCM token (see ios-setup). Sending an APNs token to FCM fails with messaging/invalid-registration-token.
  • Ask permission after a user action that explains the value; iOS shows the system prompt only once.

Foreground presentation

// capacitor.config.ts
plugins: {
  PushNotifications: {
    presentationOptions: ['badge', 'sound', 'banner', 'list'],
  },
},
  • banner + list replace the deprecated iOS alert. Capacitor 9 removes alert on iOS; replace it when upgrading. badge is iOS only.
  • Empty array = nothing shown in foreground; handle pushNotificationReceived yourself.

Capacitor version notes

Version Change
7 firebaseMessagingVersion default 24.1.0
8 firebaseMessagingVersion default 25.0.1
8.5 UIScene lifecycle. Remote-notification callbacks (didRegisterForRemoteNotificationsWithDeviceToken, didFailToRegister...) stay in AppDelegate and keep working. Do not move them to SceneDelegate. Tap routing that used AppDelegate application(_:open:) must move (see capacitor-deep-linking).
9 iOS alert presentation option removed; iOS 16 min; @main replaces @UIApplicationMain; google-services Gradle plugin 4.5.0

Verification

  1. iOS: codesign -d --entitlements :- path/to/App.app | grep aps-environment shows development (debug) or production (TestFlight/App Store).
  2. Android: adb shell dumpsys package com.example.app | grep POST_NOTIFICATIONS shows granted=true after accepting the prompt.
  3. App logs a registration token on a real device (iOS Simulator on Apple silicon can receive pushes via xcrun simctl push, but test real APNs on device).
  4. Send a test from Firebase Console (Messaging -> "Send test message") or your backend to that token.
  5. Check all three states: foreground (presentation + pushNotificationReceived), background (system tray), killed (tap -> pushNotificationActionPerformed routes correctly).
  6. iOS simulator smoke test without a server:
cat > /tmp/push.apns <<'EOF'
{ "Simulator Target Bundle": "com.example.app", "aps": { "alert": { "title": "Test", "body": "Hello" } }, "route": "/inbox" }
EOF
xcrun simctl push booted com.example.app /tmp/push.apns

(aps.alert in the APNs payload is the message content and is unaffected by the presentationOptions change.)

Error Handling

Error / symptom Cause Fix
Show the whole skill (21 more lines)

| no valid "aps-environment" entitlement string found for application | Push Notifications capability missing or profile not regenerated | Add capability in Xcode, re-sign, rebuild | | registration never fires on iOS, no error | AppDelegate does not post .capacitorDidRegisterForRemoteNotifications | Add forwarding methods to AppDelegate | | Default FirebaseApp is not initialized in this process (Android) | google-services.json missing/empty, so google-services plugin was not applied (google-services.json not found, google-services plugin not applied. Push Notifications won't work) | Put file in android/app/, rebuild | | messaging/registration-token-not-registered | Token stale (app reinstalled / data cleared) | Delete token server-side; rely on next registration | | messaging/invalid-registration-token / BadDeviceToken | APNs token sent to FCM, or sandbox token sent to production APNs | Use FCM token on iOS (ios-setup) or match APNs environment | | messaging/third-party-auth-error | APNs key not uploaded to Firebase, wrong Key ID / Team ID | Upload .p8 under Firebase Project Settings -> Cloud Messaging | | Android 13+: nothing shown, no prompt | requestPermissions() never called | Call it; manifest permission is added by the plugin | | Android shows white square icon | No monochrome notification icon | Set default_notification_icon meta-data | | Android notification silent / wrong importance | Channel created with low importance (importance is immutable after creation) | Create a new channel id; delete the old one | | Data-only message ignored when app killed (Android) | Plugin only delivers while app process lives | Native FirebaseMessagingService (android-setup) | | iOS silent / background push not delivered to JS | Plugin does not support iOS silent push | Native handling, or @capgo/capacitor-notifications silent update checks | | Duplicate class errors with Firebase after Capacitor 9 | Old core-ktx / Kotlin stdlib pins | See capacitor-app-upgrade-v8-to-v9 |

Resources