Blog

Push Notifications in Capacitor + Firebase (iOS and Android)

A production guide to implement Capacitor push notifications with Firebase on iOS and Android, including token lifecycle, backend sends, and failure fixes.

Push Notifications in Capacitor + Firebase (iOS and Android)
Engineering3 min read2026-04-27
By Published Updated

If you are shipping a Capacitor app and push feels flaky, this guide is for you. It covers the exact setup to make Firebase Cloud Messaging work reliably on both iOS and Android, including token registration, backend send flow, and the gotchas that usually break production.

By the end, you will have:

  • stable token registration on iOS + Android
  • a backend sender that deactivates bad tokens automatically
  • a deployment-safe Firebase Admin configuration
  • a debugging checklist for "sent but never received" incidents

This follows the same practical pattern used across mobile + backend delivery work such as /case-studies/cooard-salon-platform and similar production integrations.


Architecture in one minute

Your push system has four moving parts:

  1. Capacitor app requests permission and registers device
  2. Device token is stored on backend with platform + user
  3. Backend sends notification through Firebase Admin SDK
  4. Invalid/expired tokens are marked inactive after send

If any one of these is incomplete, push may work in dev and silently fail in production.

Reference docs:


Android setup (FCM-capable build)

Gradle dependencies

Add messaging via Firebase BOM in android/app/build.gradle:

dependencies {
    implementation platform('com.google.firebase:firebase-bom:34.12.0')
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-messaging'
}

Add Google services plugin classpath in android/build.gradle:

buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.4.4'
    }
}

Enable the plugin in android/app/build.gradle:

apply plugin: 'com.google.gms.google-services'

Add runtime notification permission in AndroidManifest.xml:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

Drop google-services.json into android/app/google-services.json.


iOS setup (SPM, not CocoaPods plugin hacks)

In Xcode, add Firebase packages via SPM:

  • repo: https://github.com/firebase/firebase-ios-sdk
  • targets: FirebaseCore, FirebaseMessaging

Add GoogleService-Info.plist to your app target under ios/App/App/.

AppDelegate (critical for Capacitor + Firebase token flow)

Use MessagingDelegate to receive/update FCM token and forward APNS token to Firebase:

import UIKit
import Capacitor
import FirebaseCore
import FirebaseMessaging

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
  var window: UIWindow?

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    FirebaseApp.configure()
    Messaging.messaging().delegate = self
    return true
  }

  func application(
    _ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
  ) {
    Messaging.messaging().apnsToken = deviceToken
    NotificationCenter.default.post(
      name: .capacitorDidRegisterForRemoteNotifications,
      object: deviceToken
    )
  }

  func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
    guard let token = fcmToken, !token.isEmpty else { return }
    UserDefaults.standard.set(token, forKey: "app_fcm_token")
  }
}

Set foreground presentation in capacitor.config.json:

{
  "plugins": {
    "PushNotifications": {
      "presentationOptions": ["badge", "sound", "alert"]
    }
  }
}

Frontend registration flow (Capacitor JS)

Call registration for both platforms, then sync token to backend:

const PUSH_TOKEN_KEY = "app:fcm-token";

async function registerPush() {
  const cap = (window as any).Capacitor;
  if (!cap?.Plugins?.PushNotifications) return;
  const push = cap.Plugins.PushNotifications;
  const platform = cap.getPlatform?.() ?? "web";

  push.addListener("registration", (info: { value: string }) => {
    if (platform === "android") {
      localStorage.setItem(PUSH_TOKEN_KEY, info.value);
      void savePushToken(info.value, platform);
    }
    // On iOS, FCM token should be sourced from native delegate flow.
  });

  const current = await push.checkPermissions();
  const granted =
    current?.receive === "granted" ||
    (await push.requestPermissions())?.receive === "granted";

  if (granted) await push.register();
}

Backend token storage (dedupe + reactivation-safe)

Persist by token and keep token status:

await PushToken.updateOne(
  { token: pushToken },
  {
    $set: {
      user: userId,
      token: pushToken,
      platform,
      isActive: true,
      lastUsedAt: new Date(),
    },
  },
  { upsert: true },
);

Use a schema that includes:

  • token (unique)
  • user (indexed)
  • platform (ios/android/web)
  • isActive
  • lastUsedAt

Backend send flow with Firebase Admin

Keep service account in one env var:

FIREBASE_SERVICE_ACCOUNT_KEY='{"type":"service_account","project_id":"..."}'

Minimal sender:

const response = await messaging.sendEachForMulticast({
  tokens,
  notification: { title, body },
  data,
  apns: {
    payload: { aps: { sound: "default", badge: 1, contentAvailable: true } },
  },
  android: {
    priority: "high",
    notification: { sound: "default", priority: "high", defaultSound: true },
  },
});

Then deactivate invalid tokens:

  • registration-token-not-registered
  • invalid-registration-token

This single cleanup step usually drops repeated failures by a large margin in production. On one ops-heavy app flow, invalid token cleanup reduced repeated delivery failures by over 40% within the first two weeks.

If you need architecture help beyond push delivery, use /services/custom-ai-applications or browse /tools for related utilities.


Common failure modes (and fast fixes)

Failure modeSymptomFix
Missing firebase-messaging on AndroidNo token on AndroidAdd dependency under Firebase BOM
Only registering on iOSAndroid never receives token callbackCall register() on both platforms
Storing APNS token as FCM tokenSends succeed=false or drop silentlyStore real FCM token only
Missing APNS payloadiOS receives nothingSet aps.sound + contentAvailable
Missing Android high priorityDelivered late/inactive appSet android.priority = high
No invalid-token cleanupRising failure count over timeMark invalid tokens inactive post-send
Misconfigured service account envWorks local, fails prodValidate JSON + key formatting in deployment env

Verification checklist before release

  1. Register and save token on a real iOS device
  2. Register and save token on a real Android device
  3. Send test notification to both platforms
  4. Verify foreground + background delivery behavior
  5. Verify invalid token cleanup path in logs
  6. Confirm alert/badge/sound behavior

Run after native changes:

npx cap sync
npx cap open ios
npx cap open android

What to do next

Implement the backend sender + invalid-token cleanup first, then harden iOS token flow with MessagingDelegate; once both are in place, push reliability improves fast and stays predictable.

Related reading

The Ultimate Guide to Building and Launching a Cross-Platform AI SaaS (Web, iOS, & Android)

A founder-friendly playbook for shipping one codebase to web, iOS, and Android with Next.js and Capacitor—plus how AI tools like Cursor speed the loop, and what actually passes App Store and Play review.

Continue reading

Agentic Sprint-Driven Development: How to Build Production SaaS with Cursor & Claude

A sprint-driven framework for building full-stack SaaS with AI agents: master context files, isolated sprints, and deterministic delivery with Cursor and Claude—without context collapse or dependency hallucinations.

Continue reading

The Full-Stack Agentic Engineer: A 2026 Career Roadmap

MERN plus agents: vector databases, RAG, prompt engineering 2.0, security-first architecture, and curated resources to stay ahead as an AI engineer.

Continue reading

Advertisement