How to Add Push Notifications to a Flutter App with Firebase Cloud Messaging
Wire Firebase Cloud Messaging into a Flutter app from zero: add firebase_messaging, request iOS and Android permissions, capture the device token, and handle foreground, background, and terminated messages. Do it by hand below, or flip it on in The Flutter Kit with FCM diagnostics and copyable tokens.
To add push notifications to a Flutter app, you install firebase_messaging, request notification permission on iOS and Android 13+, capture the device's FCM registration token, and register three handlers for foreground, background, and terminated-state messages. FCM is the free transport that reaches both stores from one Dart codebase. The Flutter Kit ($69 one-time, unlimited projects, lifetime updates, full source ownership) ships this pre-wired with local + FCM push diagnostics and copyable tokens, so you test a real notification on a device in minutes instead of fighting APNs entitlements.
Why the message lifecycle is where DIY push breaks
The getToken-to-onMessage snippet looks like a ten-minute job, and that is the trap. The real cost of adding push notifications to a Flutter app lives in the three app states, because each is a different code path that fails independently. Foreground messages never draw a system banner on their own, so you must render one yourself or they appear to vanish. Background and terminated messages execute in a separate Dart isolate, which is why the handler has to be top-level, annotated with vm:entry-point, and re-initialize Firebase — get this wrong and data pushes silently no-op in release builds. On iOS, none of it works at all until an APNs auth key is uploaded and the Push Notifications plus Background Modes capabilities are enabled, a step that compiles fine and then delivers nothing. Add token rotation, Android notification channels, and permission prompts, and a 'quick' push feature becomes a day of device testing. The Flutter Kit's value here is not the listener code; it is the diagnostics screen that shows live permission status, a copyable token, and one-tap test sends, so you see exactly which state is broken instead of guessing.
- Foreground messages need flutter_local_notifications or no banner shows
- Background/terminated handlers run in a separate isolate and must be top-level
- iOS delivers nothing until an APNs key and capabilities are configured
- Tokens rotate — onTokenRefresh must upsert or sends silently fail
When you should wire FCM by hand instead
Honesty first: if all you need is a single local reminder notification with no server push, skip FCM entirely and use flutter_local_notifications on its own — pulling in Firebase Messaging and APNs setup is overkill for a scheduled alarm. The same is true if you've already standardized on a different provider such as OneSignal for its segmentation and dashboard, where layering raw FCM underneath only duplicates work. And if your app deliberately avoids Firebase, hand-rolling the messaging layer (or going straight to APNs and FCM HTTP v1 from your own backend) keeps your dependency surface clean. The Flutter Kit earns its $69 when you want push as one part of a real app — tied to Firebase Auth, Firestore-stored tokens, and go_router deep links — already assembled and verified on a device, with the diagnostics screen that turns 'why isn't this arriving' from a lost afternoon into a glance.
Wire Firebase Cloud Messaging into Flutter, step by step
This is the from-zero FCM path. Each step is a piece of plumbing The Flutter Kit already ships behind a diagnostics screen — do it by hand to understand the message lifecycle, then decide whether you want to maintain the APNs and channel setup yourself.
- 1
Add firebase_messaging and connect FlutterFire
Add firebase_messaging (plus flutter_local_notifications to render foreground banners) and run flutterfire configure so firebase_options.dart exists for every platform. On iOS you also need an APNs key uploaded to the Firebase console and the Push Notifications capability enabled in Xcode — FCM on iOS is a thin layer over APNs and silently does nothing without it.
flutter pub add firebase_messaging flutter_local_notifications dart pub global activate flutterfire_cli flutterfire configure - 2
Request notification permission the platform way
iOS and Android 13+ both gate notifications behind a runtime permission prompt. Call requestPermission() and check the authorizationStatus before assuming delivery works — a denied prompt is the number-one reason 'my pushes don't arrive'. The kit surfaces the live permission status on its diagnostics screen so you never guess.
final settings = await FirebaseMessaging.instance.requestPermission( alert: true, badge: true, sound: true, ); if (settings.authorizationStatus == AuthorizationStatus.denied) { // route the user to system settings } - 3
Get the FCM token and watch it refresh
The registration token is the address you send a push to. Read it with getToken(), persist it to Firestore against the user, and also listen to onTokenRefresh — tokens rotate on reinstall, restore, and data clears, and a stale token means silent delivery failures. The kit makes the current token copyable so you can paste it straight into a test send.
final token = await FirebaseMessaging.instance.getToken(); FirebaseMessaging.instance.onTokenRefresh.listen((newToken) { // upsert newToken to the user's doc in Firestore }); - 4
Handle foreground messages explicitly
When the app is in the foreground, FCM does NOT draw a system notification for you — onMessage fires with the payload and you decide what to show. Most apps render it via flutter_local_notifications so a banner still appears. Forget this and your pushes look broken only while the app is open, which is maddening to debug.
FirebaseMessaging.onMessage.listen((RemoteMessage message) { final n = message.notification; if (n != null) { localNotifications.show( n.hashCode, n.title, n.body, channelDetails); } }); - 5
Register a top-level background handler
Background and terminated messages run in a separate isolate, so the handler MUST be a top-level or static function annotated with @pragma('vm:entry-point') and registered before runApp. You also call Firebase.initializeApp() inside it because the isolate has no app instance. This isolate gotcha is the single most common FCM mistake in DIY Flutter builds.
@pragma('vm:entry-point') Future<void> _bgHandler(RemoteMessage message) async { await Firebase.initializeApp(); // process data payload, e.g. update a local DB } void main() { FirebaseMessaging.onBackgroundMessage(_bgHandler); runApp(const App()); } - 6
Handle taps that open the app (background + terminated)
A user tapping a notification is a navigation event. Use onMessageOpenedApp for taps when the app was backgrounded, and getInitialMessage() for a cold start from terminated — the latter returns the message that launched the app so you can deep-link to the right screen. In the kit this routes through go_router so a tapped push lands on the correct route.
FirebaseMessaging.onMessageOpenedApp.listen((m) => _goTo(m.data['route'])); final initial = await FirebaseMessaging.instance.getInitialMessage(); if (initial != null) _goTo(initial.data['route']); - 7
Send a real test and verify it lands
Confirm the full loop before writing any backend. Copy the device token and send a test from the Firebase console (Cloud Messaging > test message) or curl the FCM v1 API. Verify foreground, background, and terminated states separately — they exercise different code paths. The Flutter Kit's diagnostics screen does this with one tap so you prove delivery on a real device immediately.
curl -X POST https://fcm.googleapis.com/v1/projects/PROJECT_ID/messages:send \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"message":{"token":"DEVICE_FCM_TOKEN","notification":{"title":"Hi","body":"It works"}}}'
Frequently Asked Questions
Do I still need firebase_messaging if I use The Flutter Kit's push notifications?
Why do push notifications arrive on Android but not iOS in my Flutter app?
How do I handle a terminated-state push notification tap in Flutter?
Why doesn't a banner show for foreground push notifications in my Flutter app?
Why must the FCM background handler be a top-level function in Flutter?
Can I test push notifications in The Flutter Kit without building a backend first?
Keep exploring
Skip the APNs and isolate gotchas
The Flutter Kit ships Firebase Cloud Messaging pre-wired — permissions, token capture, foreground/background/terminated handlers, go_router deep links, and a diagnostics screen with copyable tokens. $69 one-time, unlimited projects, lifetime updates, full source ownership. Prove a real push on a device in minutes.
Get The Flutter Kit — $69One-time purchase · Lifetime updates · Unlimited projects