agentleFS
Cap-goCap-go/capgo-skills/skills/capacitor-push-notifications/SKILL.md

capacitor-push-notifications

Skill75 starsChanged 33 days agoMITView on GitHub

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).

  • Installs packages

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Only load a reference when its topic is in play.

    line 45
  2. Very specific:

    Call initPush() at bootstrap so pushNotificationActionPerformed from a cold-start tap is captured.

    line 76
  3. Distinctive:

    Ask permission after a user action that explains the value.

    line 79
  4. Very specific:

    Check all three states: foreground (presentation + pushNotificationReceived), background (system tray), killed (tap -> …

    line 110

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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)

NeedPlugin
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

VersionChange
7firebaseMessagingVersion default 24.1.0
8firebaseMessagingVersion default 25.0.1
8.5UIScene 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).
9iOS 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 / symptomCauseFix
no valid "aps-environment" entitlement string found for applicationPush Notifications capability missing or profile not regeneratedAdd capability in Xcode, re-sign, rebuild
registration never fires on iOS, no errorAppDelegate does not post .capacitorDidRegisterForRemoteNotificationsAdd 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-registeredToken stale (app reinstalled / data cleared)Delete token server-side; rely on next registration
messaging/invalid-registration-token / BadDeviceTokenAPNs token sent to FCM, or sandbox token sent to production APNsUse FCM token on iOS (ios-setup) or match APNs environment
messaging/third-party-auth-errorAPNs key not uploaded to Firebase, wrong Key ID / Team IDUpload .p8 under Firebase Project Settings -> Cloud Messaging
Android 13+: nothing shown, no promptrequestPermissions() never calledCall it; manifest permission is added by the plugin
Android shows white square iconNo monochrome notification iconSet default_notification_icon meta-data
Android notification silent / wrong importanceChannel 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 livesNative FirebaseMessagingService (android-setup)
iOS silent / background push not delivered to JSPlugin does not support iOS silent pushNative handling, or @capgo/capacitor-notifications silent update checks
Duplicate class errors with Firebase after Capacitor 9Old core-ktx / Kotlin stdlib pinsSee capacitor-app-upgrade-v8-to-v9

Resources

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.