Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Create Custom Push Notifications in FlutterFlow

FlutterFlow uses Firebase Cloud Messaging for push notifications. You can customize notification appearance by structuring your FCM payload with title, body, image, and data fields. Add Android notification channels for categorization, configure custom sounds, and use the onMessage handler to intercept foreground notifications and display custom in-app UI.

What you'll learn

  • How to structure rich FCM notification payloads with images and action data
  • How to create Android notification channels for categorized alerts
  • How to handle foreground notifications with a custom in-app banner
  • How to add custom notification sounds to iOS and Android
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read30-40 minFlutterFlow Free+ (Firebase required)March 2026RapidDev Engineering Team
TL;DR

FlutterFlow uses Firebase Cloud Messaging for push notifications. You can customize notification appearance by structuring your FCM payload with title, body, image, and data fields. Add Android notification channels for categorization, configure custom sounds, and use the onMessage handler to intercept foreground notifications and display custom in-app UI.

Beyond the Default Notification

FlutterFlow's built-in Send Push Notification action sends a basic FCM message — but FCM supports far richer payloads. By structuring your notification payload carefully and adding custom code for channel setup and foreground handling, you can create notifications that feel native to your brand: category-specific sounds, large images, deep-link action buttons, and in-app banners that match your UI instead of the OS default.

Prerequisites

  • FlutterFlow project with Firebase connected (Settings > Firebase)
  • Push Notifications enabled in Settings > Push Notifications
  • FCM token stored in your users Firestore document
  • Android notification channel concept understanding (optional but helpful)

Step-by-step guide

1

Define Android Notification Channels in Custom Code

Android 8+ requires notification channels before you can display categorized notifications. In FlutterFlow, open Custom Code > Custom Actions and create a new action called initNotificationChannels. This Dart code runs once at app startup and registers channels like 'orders', 'promotions', and 'alerts' with distinct importance levels. Each channel can have a different sound, vibration pattern, and lock-screen visibility. Without channels, all your notifications land in the default bucket with no customization possible.

init_notification_channels.dart
1import 'package:flutter_local_notifications/flutter_local_notifications.dart';
2
3Future<void> initNotificationChannels() async {
4 final FlutterLocalNotificationsPlugin plugin =
5 FlutterLocalNotificationsPlugin();
6
7 const AndroidNotificationChannel ordersChannel = AndroidNotificationChannel(
8 'orders',
9 'Order Updates',
10 description: 'Notifications about your order status',
11 importance: Importance.high,
12 sound: RawResourceAndroidNotificationSound('order_chime'),
13 );
14
15 const AndroidNotificationChannel promoChannel = AndroidNotificationChannel(
16 'promotions',
17 'Promotions',
18 description: 'Deals and promotional offers',
19 importance: Importance.low,
20 );
21
22 await plugin
23 .resolvePlatformSpecificImplementation<
24 AndroidFlutterLocalNotificationsPlugin>()
25 ?.createNotificationChannel(ordersChannel);
26
27 await plugin
28 .resolvePlatformSpecificImplementation<
29 AndroidFlutterLocalNotificationsPlugin>()
30 ?.createNotificationChannel(promoChannel);
31}

Expected result: On Android, your app's notification settings screen shows separate 'Order Updates' and 'Promotions' channels users can toggle independently.

2

Structure Your FCM Payload for Rich Notifications

When sending notifications via your Cloud Function or the Firebase Console, structure the payload to include both the notification object (for system tray display) and a data object (for your app logic). The notification object holds title, body, and image. The data object carries action type, target page, and any IDs your app needs for deep linking. Keeping these two objects separate ensures the notification displays correctly even when the app is closed, while your app still receives the data payload to navigate correctly when tapped.

fcm_payload.js
1// Cloud Function payload example
2const payload = {
3 notification: {
4 title: 'Your order has shipped!',
5 body: 'Estimated delivery: Tomorrow by 5pm',
6 image: 'https://yourcdn.com/shipping-icon.png'
7 },
8 data: {
9 action: 'navigate',
10 target_page: 'OrderDetail',
11 order_id: orderId,
12 channel_id: 'orders'
13 },
14 android: {
15 notification: {
16 channel_id: 'orders',
17 sound: 'order_chime',
18 click_action: 'FLUTTER_NOTIFICATION_CLICK'
19 }
20 },
21 apns: {
22 payload: {
23 aps: {
24 sound: 'order_chime.aiff',
25 badge: 1
26 }
27 }
28 },
29 token: userFcmToken
30};

Expected result: Notifications arrive with the shipping image visible in the system tray and tapping them passes the order_id data to your app.

3

Add Custom Sounds to iOS and Android

For iOS, place your .aiff sound file inside the Runner/Resources folder in your downloaded Flutter project, then re-upload via code export. For Android, place your .mp3 or .wav file in android/app/src/main/res/raw/. In FlutterFlow, go to Custom Files and upload the sound assets, specifying the correct platform path. Reference the sound by filename (without extension) in your Android channel setup and FCM payload. On iOS, the sound name in the APNS payload must exactly match the filename including the .aiff extension.

Expected result: Order notifications play your custom chime sound while promotional notifications use the softer default tone.

4

Handle Foreground Notifications with a Custom In-App Banner

By default, FCM does not show a system notification when your app is in the foreground — it only delivers the message silently to your code. To show something to the user, add a Custom Action that subscribes to FirebaseMessaging.onMessage stream. When a message arrives, update a Page State variable (e.g., activeNotificationBanner) with the notification content. Wire this state variable to a conditionally visible banner widget at the top of your main scaffold. Use an AnimationController to slide the banner in, auto-dismiss it after 4 seconds, and let the user tap it to navigate to the target page.

foreground_notification_listener.dart
1import 'package:firebase_messaging/firebase_messaging.dart';
2
3void listenForegroundMessages(
4 Future<void> Function(Map<String, dynamic> data) onMessage,
5) {
6 FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
7 final data = {
8 'title': message.notification?.title ?? '',
9 'body': message.notification?.body ?? '',
10 'action': message.data['action'] ?? '',
11 'target_page': message.data['target_page'] ?? '',
12 'payload': message.data,
13 };
14 await onMessage(data);
15 });
16}

Expected result: When a notification arrives while the app is open, a branded banner slides down from the top of the screen instead of the generic OS notification shade.

5

Add Action Buttons to Android Notifications

Android supports up to three action buttons on notifications without the user needing to open the app. In your Android channel-aware payload, add an android.notification.actions array with title and action fields. In your app's notification tap handler (FirebaseMessaging.onMessageOpenedApp), check the action field on the message to determine which button was tapped and execute the right logic — for example 'Mark as Read' vs 'View Details'. Register these action categories in your custom notification setup code.

Expected result: Order notifications show 'Track Order' and 'Dismiss' buttons directly in the notification shade — users take action without launching the app.

Complete working example

custom_notification_setup.dart
1import 'package:firebase_messaging/firebase_messaging.dart';
2import 'package:flutter_local_notifications/flutter_local_notifications.dart';
3
4// ─── Channel Definitions ────────────────────────────────────────────────────
5
6const AndroidNotificationChannel ordersChannel = AndroidNotificationChannel(
7 'orders',
8 'Order Updates',
9 description: 'Real-time updates about your orders',
10 importance: Importance.high,
11 sound: RawResourceAndroidNotificationSound('order_chime'),
12 enableVibration: true,
13);
14
15const AndroidNotificationChannel promoChannel = AndroidNotificationChannel(
16 'promotions',
17 'Promotions',
18 description: 'Deals and special offers',
19 importance: Importance.low,
20 playSound: false,
21);
22
23const AndroidNotificationChannel alertsChannel = AndroidNotificationChannel(
24 'alerts',
25 'Important Alerts',
26 description: 'Security and account alerts',
27 importance: Importance.max,
28 enableVibration: true,
29);
30
31// ─── Initialization ──────────────────────────────────────────────────────────
32
33Future<void> initNotificationChannels() async {
34 final FlutterLocalNotificationsPlugin plugin =
35 FlutterLocalNotificationsPlugin();
36
37 final androidImpl = plugin.resolvePlatformSpecificImplementation<
38 AndroidFlutterLocalNotificationsPlugin>();
39
40 await androidImpl?.createNotificationChannel(ordersChannel);
41 await androidImpl?.createNotificationChannel(promoChannel);
42 await androidImpl?.createNotificationChannel(alertsChannel);
43
44 // Request iOS permissions
45 await FirebaseMessaging.instance.requestPermission(
46 alert: true,
47 badge: true,
48 sound: true,
49 provisional: false,
50 );
51
52 // Handle background tap — navigate when app is terminated
53 final RemoteMessage? initial =
54 await FirebaseMessaging.instance.getInitialMessage();
55 if (initial != null) {
56 _handleNotificationTap(initial.data);
57 }
58
59 // Handle background → foreground tap
60 FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
61 _handleNotificationTap(message.data);
62 });
63}
64
65// ─── Navigation Handler ──────────────────────────────────────────────────────
66
67void _handleNotificationTap(Map<String, dynamic> data) {
68 final String targetPage = data['target_page'] ?? '';
69 final String id = data['id'] ?? '';
70 // Dispatch to your FlutterFlow navigation action
71 // using FFAppState or a navigation helper
72 FFAppState().pendingNavPage = targetPage;
73 FFAppState().pendingNavId = id;
74}

Common mistakes when creating Custom Push Notifications in FlutterFlow

Why it's a problem: Sending all notifications on the default channel with no categorization

How to avoid: Create distinct Android notification channels for each category (orders, promos, alerts) so users control each independently from their device settings.

Why it's a problem: Not handling the onMessageOpenedApp vs getInitialMessage distinction

How to avoid: Always call getInitialMessage() during app initialization AND listen to onMessageOpenedApp to cover both cold-start and background-to-foreground scenarios.

Why it's a problem: Placing notification logic inside a FlutterFlow page instead of a persistent service

How to avoid: Register all FCM listeners in main.dart or a top-level Custom Action called once at app launch, outside any specific page.

Why it's a problem: Using the same sound file name on iOS and Android without format conversion

How to avoid: Convert your sound to both .aiff (iOS) and .mp3 (Android) and reference the correct filename with extension in each platform's notification payload.

Best practices

  • Always request notification permission with a contextual explanation — explain why your app needs notifications before triggering the OS permission dialog.
  • Store FCM tokens in Firestore and refresh them using FirebaseMessaging.instance.onTokenRefresh to handle token rotation.
  • Limit notification frequency: more than 3 per day for non-critical categories causes users to disable all notifications.
  • Always include a data payload alongside the notification object so your app can deep-link correctly regardless of whether it was open, backgrounded, or closed.
  • Use collapse_key in your FCM payload so that multiple unread notifications of the same type stack as one entry in the notification shade.
  • Test notifications on both iOS simulator (requires physical device for actual delivery) and Android emulator — behavior differs significantly.
  • Log notification open events to Firestore so you can measure click-through rate by channel and refine your messaging strategy.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a FlutterFlow app with Firebase. Explain how to create Android notification channels in Dart, handle FCM foreground messages with a custom in-app banner, and structure an FCM payload that includes a large image and action buttons. Show code for all three.

FlutterFlow Prompt

In my FlutterFlow app, add a Custom Action that initializes three Android notification channels (orders, promotions, alerts) with different importance levels. Then add a foreground message listener that updates a Page State variable called activeNotification with the message title, body, and target page for in-app banner display.

Frequently asked questions

Does FlutterFlow support notification action buttons out of the box?

No. FlutterFlow's built-in Send Push Notification action sends a basic title and body only. Action buttons require a custom FCM payload sent from a Cloud Function and a Custom Action in your app to handle the button tap responses.

Why does my notification image not appear on iOS?

iOS requires a Notification Service Extension to download and display images in rich notifications. Without this extension, the image field is ignored. You need to add the extension via Xcode after exporting your FlutterFlow project as code.

Can I send notifications without a backend Cloud Function?

You can send test notifications from the Firebase Console. For production use, you should always use a server-side Cloud Function with your service account credentials — never embed your FCM server key in client code.

What is the difference between notification payload and data payload in FCM?

The notification payload is handled by the OS and displays automatically in the system tray even when your app is closed. The data payload is delivered silently to your app code for processing. Best practice is to include both: notification for display, data for deep-linking logic.

How do I test custom notification sounds before releasing my app?

On Android, place your sound file in android/app/src/main/res/raw/ and send a test notification from the Firebase Console with the channel ID specified. On iOS, you need a physical device — the simulator cannot play custom notification sounds.

Why are my foreground notifications not showing anything?

FCM suppresses system tray notifications when your app is in the foreground. You must listen to the FirebaseMessaging.onMessage stream and build your own in-app notification UI — a sliding banner, a badge update, or a dialog — to alert the user.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.