# How to Create Custom Push Notifications in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 30-40 min
- Compatibility: FlutterFlow Free+ (Firebase required)
- Last updated: March 2026

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

## Before you start

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

```
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

Future<void> initNotificationChannels() async {
  final FlutterLocalNotificationsPlugin plugin =
      FlutterLocalNotificationsPlugin();

  const AndroidNotificationChannel ordersChannel = AndroidNotificationChannel(
    'orders',
    'Order Updates',
    description: 'Notifications about your order status',
    importance: Importance.high,
    sound: RawResourceAndroidNotificationSound('order_chime'),
  );

  const AndroidNotificationChannel promoChannel = AndroidNotificationChannel(
    'promotions',
    'Promotions',
    description: 'Deals and promotional offers',
    importance: Importance.low,
  );

  await plugin
      .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(ordersChannel);

  await plugin
      .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(promoChannel);
}
```

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

```
// Cloud Function payload example
const payload = {
  notification: {
    title: 'Your order has shipped!',
    body: 'Estimated delivery: Tomorrow by 5pm',
    image: 'https://yourcdn.com/shipping-icon.png'
  },
  data: {
    action: 'navigate',
    target_page: 'OrderDetail',
    order_id: orderId,
    channel_id: 'orders'
  },
  android: {
    notification: {
      channel_id: 'orders',
      sound: 'order_chime',
      click_action: 'FLUTTER_NOTIFICATION_CLICK'
    }
  },
  apns: {
    payload: {
      aps: {
        sound: 'order_chime.aiff',
        badge: 1
      }
    }
  },
  token: userFcmToken
};
```

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

```
import 'package:firebase_messaging/firebase_messaging.dart';

void listenForegroundMessages(
  Future<void> Function(Map<String, dynamic> data) onMessage,
) {
  FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
    final data = {
      'title': message.notification?.title ?? '',
      'body': message.notification?.body ?? '',
      'action': message.data['action'] ?? '',
      'target_page': message.data['target_page'] ?? '',
      'payload': message.data,
    };
    await onMessage(data);
  });
}
```

**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 code example

File: `custom_notification_setup.dart`

```dart
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

// ─── Channel Definitions ────────────────────────────────────────────────────

const AndroidNotificationChannel ordersChannel = AndroidNotificationChannel(
  'orders',
  'Order Updates',
  description: 'Real-time updates about your orders',
  importance: Importance.high,
  sound: RawResourceAndroidNotificationSound('order_chime'),
  enableVibration: true,
);

const AndroidNotificationChannel promoChannel = AndroidNotificationChannel(
  'promotions',
  'Promotions',
  description: 'Deals and special offers',
  importance: Importance.low,
  playSound: false,
);

const AndroidNotificationChannel alertsChannel = AndroidNotificationChannel(
  'alerts',
  'Important Alerts',
  description: 'Security and account alerts',
  importance: Importance.max,
  enableVibration: true,
);

// ─── Initialization ──────────────────────────────────────────────────────────

Future<void> initNotificationChannels() async {
  final FlutterLocalNotificationsPlugin plugin =
      FlutterLocalNotificationsPlugin();

  final androidImpl = plugin.resolvePlatformSpecificImplementation<
      AndroidFlutterLocalNotificationsPlugin>();

  await androidImpl?.createNotificationChannel(ordersChannel);
  await androidImpl?.createNotificationChannel(promoChannel);
  await androidImpl?.createNotificationChannel(alertsChannel);

  // Request iOS permissions
  await FirebaseMessaging.instance.requestPermission(
    alert: true,
    badge: true,
    sound: true,
    provisional: false,
  );

  // Handle background tap — navigate when app is terminated
  final RemoteMessage? initial =
      await FirebaseMessaging.instance.getInitialMessage();
  if (initial != null) {
    _handleNotificationTap(initial.data);
  }

  // Handle background → foreground tap
  FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
    _handleNotificationTap(message.data);
  });
}

// ─── Navigation Handler ──────────────────────────────────────────────────────

void _handleNotificationTap(Map<String, dynamic> data) {
  final String targetPage = data['target_page'] ?? '';
  final String id = data['id'] ?? '';
  // Dispatch to your FlutterFlow navigation action
  // using FFAppState or a navigation helper
  FFAppState().pendingNavPage = targetPage;
  FFAppState().pendingNavId = id;
}
```

## Common mistakes

- **Sending all notifications on the default channel with no categorization** — Users cannot selectively disable notification types — either they get everything or nothing. This leads to unsubscribes. Fix: Create distinct Android notification channels for each category (orders, promos, alerts) so users control each independently from their device settings.
- **Not handling the onMessageOpenedApp vs getInitialMessage distinction** — getInitialMessage() handles taps when the app was fully terminated. If you only listen to onMessageOpenedApp, taps from a cold start are silently ignored. Fix: Always call getInitialMessage() during app initialization AND listen to onMessageOpenedApp to cover both cold-start and background-to-foreground scenarios.
- **Placing notification logic inside a FlutterFlow page instead of a persistent service** — Page-scoped listeners are destroyed when the page unmounts, so you miss notifications after navigation. Fix: Register all FCM listeners in main.dart or a top-level Custom Action called once at app launch, outside any specific page.
- **Using the same sound file name on iOS and Android without format conversion** — iOS requires .aiff or .caf format. Android uses .mp3 or .wav in the res/raw directory. The same .mp3 placed in the iOS bundle is silently ignored. Fix: 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.

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

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-can-i-create-custom-push-notifications-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-can-i-create-custom-push-notifications-in-flutterflow
