Skip to main content
skill CX Codex

capacitor-push-notifications · cap-go — Claude Code skill for Capacitor push

A skill for setting up and debugging remote push in Capacitor apps: FCM on Android, APNs on iOS. Useful when registration never fires or notifications don't show.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add Cap-go/capgo-skills --skill capacitor-push-notifications -a claude-code
Open on GitHub

What capacitor-push-notifications does

The skill walks the agent through the setup: it inspects package.json, capacitor.config.*, AppDelegate.swift, *.entitlements, google-services.json and AndroidManifest.xml, then installs @capacitor/push-notifications with npm install and npx cap sync. iOS needs the Push Notifications capability, an APNs key and token forwarding in AppDelegate; Android needs google-services.json, a channel and an icon. Verification runs on physical devices.

JS registration has a fixed order. Listeners for registration, registrationError, pushNotificationReceived and pushNotificationActionPerformed go up before register(), otherwise the first token is missed. Permission is a separate step and is required on iOS and Android 13+. Tokens rotate, so every registration value should reach the server and be upserted by user and device.

The iOS trap: the plugin reports the raw APNs token unless AppDelegate swaps in the FCM token. Send an APNs token to FCM and you get messaging/invalid-registration-token. The skill also covers presentationOptions — Capacitor 9 removes the iOS alert option, so use banner and list. Under the Capacitor 8.5 UIScene lifecycle the remote-notification callbacks stay in AppDelegate; don't move them to SceneDelegate. Local-only notifications and deep link handling are out of scope.

What it helps with

  • add remote push to a Capacitor app
  • fix the case where registration never fires
  • swap an APNs token for an FCM token on iOS
  • grant the POST_NOTIFICATIONS permission on Android 13+
  • show notifications in the foreground via presentationOptions

Example prompts for the agent

  • Set up remote push in my Capacitor app: FCM on Android, APNs on iOS
  • registration never fires — help me debug the token
  • iOS returns an APNs token instead of FCM, what should I change in AppDelegate?

How to install capacitor-push-notifications

1Skills CLI (Vercel)

npx skills add Cap-go/capgo-skills --skill capacitor-push-notifications -a claude-code

2Manually: put the files in place

~/.claude/skills/capacitor-push-notifications/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/capacitor-push-notifications/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about capacitor-push-notifications

How do I install capacitor-push-notifications in Claude?

Run npx skills add Cap-go/capgo-skills --skill capacitor-push-notifications -a claude-code. The skill lands in ~/.claude/skills/capacitor-push-notifications/SKILL.md.

What does capacitor-push-notifications do?

It guides the agent through remote push setup in Capacitor: plugin install, the APNs key and capability on iOS, google-services.json and a channel on Android, the JS registration order, and device verification.

How is it different from the neighbouring skills?

It covers remote push only. Local notifications, deep link handling, UIScene migration and log capture each have their own skill.

Source text

SKILL.md · MIT
---
name: capacitor-push-notifications
description: 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).
---

# 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](references/ios-setup.md).
4. **Android**: `google-services.json`, channel, icon, permission. Details: [references/android-setup.md](references/android-setup.md).
5. **JS**: listeners first, then permission, then `register()` (below).
6. **Backend**: send a test message. Payload shapes: [references/sending.md](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)

```typescript
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

```ts
// 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:

```bash
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 |
|-----------------|-------|-----|
| `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

- Plugin API: https://capacitorjs.com/docs/apis/push-notifications
- Firebase guide: https://capacitorjs.com/docs/guides/push-notifications-firebase
- Updating to 9.0: https://capacitorjs.com/docs/updating/9-0
- Capgo Firebase Messaging: https://capgo.app/docs/plugins/firebase-messaging/
- Capgo Notifications: https://capgo.app/docs/plugins/notifications/

© Cap-go · MIT · Cap-go/capgo-skills

Similar skills

Everything for Mobile →

capacitor-push-notifications by other authors comes first.

capacitor-push-notifications capawesome-team Walks the agent through Firebase, the @capacitor-firebase/messaging plugin, Android, iOS and Web setup, APNs, FCM tokens and a test send. CX Codex +1 979installs push-notifications dpearson2699 Review, build, and debug local and APNs remote notifications for iOS and macOS in Swift. Handles permission flows, silent pushes, and service extensions. CX Codex +1 4,200installs push-notification-setup secondsky A skill that walks your agent through push notification setup with Firebase Cloud Messaging and native services. Useful when you add notifications to an app or debug background messages. CX Codex +1 418installs capacitor-plugins capawesome-team A skill that walks the agent through installing and configuring Capacitor plugins from six sources, official and Capawesome among them. CX Codex +1 1,800installs capacitor-expert capawesome-team A Capacitor reference skill for AI agents: architecture, CLI, plugins, framework integration and Capawesome Cloud. Useful when the agent builds or debugs a cross-platform app. CX Codex +1 1,100installs limrun-expo-development limrun-inc Prepares and runs Expo and React Native apps on Limrun: dev-client build, Metro tunnel, JS and TS iteration without repeated native rebuilds. CX Codex +1 43,100installs argent-metro-debugger software-mansion A skill that connects a debugger to a JS runtime via CDP. Inspect React components, read console logs, and evaluate code live. Works with React Native (iOS, Android, Vega) and Chromium apps (Electron, browsers). CX Codex +1 26,300installs

From the same set29

safe-area-handling Fixes Capacitor layouts where content sits under the notch, Dynamic Island, home indicator or the Android gesture bar. CX Codex +1 1,300installs capacitor-app-store Walks a Capacitor build from "runs on my device" to live in the App Store and Google Play: versions, signing, IPA/AAB upload, TestFlight and Play tracks. CX Codex +1 1,200installs capacitor-accessibility A skill that walks an AI agent through accessibility in Capacitor apps: VoiceOver, TalkBack, Dynamic Type, Switch Control and Android font scale. CX Codex +1 1,200installs capacitor-security Audits the app layer of a Capacitor project for leaked secrets, weak on-device storage and cleartext traffic, then walks through the fixes. CX Codex +1 1,200installs capacitor-splash-screen A skill that traces why a Capacitor app flashes white at launch or never hides its splash, then fixes the config, Android 12+ theme and iOS storyboard. CX Codex +1 1,100installs capacitor-keyboard Walks through on-screen keyboard behavior in Capacitor apps: iOS resize modes, keyboard event heights, Android edge-to-edge. Useful when inputs hide behind the keyboard. CX Codex +1 1,000installs tailwind-capacitor Sets up Tailwind CSS v4 in Capacitor apps and covers the mobile layout traps: safe areas, dark mode, iOS and Android variants. CX Codex +1 955installs framework-to-capacitor Turns a Next.js, Nuxt, Angular, SvelteKit or Vite build into a static bundle Capacitor can ship as an iOS and Android app. CX Codex +1 870installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor