Flutter push notifications

Contents

Set up Workflows push notifications in the Flutter SDK. For the concept and channel setup, see Push notifications.

Available in the Flutter SDK version 5.35.0 and newer. Not supported on Flutter Web or macOS.

Requirements

Configure push in your app as usual (for example with firebase_messaging) and request notification permission from the user.

Automatic registration and open tracking (default)

Both behaviors are on by default. An app that already has push configured starts sending its device token to PostHog after upgrading, with no code change:

Dart
final config = PostHogConfig('<ph_project_token>');
// Both default to true, shown here for clarity:
config.capturePushNotificationSubscriptions = true; // register the device token with PostHog
config.capturePushNotificationOpened = true; // capture `$push_notification_opened` on tap
await Posthog().setup(config);

On iOS the native SDK hooks the app delegate's remote-notification registration callback, so it picks up the APNs token once your app registers for remote notifications. On Android it fetches the FCM token at startup when firebase-messaging is on the classpath. The token is registered under the current distinct ID, so it follows the user across identify().

Every tap on a remote notification is captured on both platforms, whether the notification cold-launched the app or it was already running. Locally-scheduled notifications are ignored — capture those manually (below).

Cold-start capture needs posthog-ios 3.72.0 on iOS and posthog-android 3.62.0 on Android, which the plugin's version floors bring in. On Android a tap is recognized by the google.message_id extra that Firebase puts on the intent, so push delivered outside FCM isn't seen.

iOS needs a notification delegate

iOS only reports a notification tap to your app through UNUserNotificationCenter.current().delegate. A stock Flutter app sets none, and flutter_local_notifications doesn't set one either — so without this, iOS reports the tap to nobody and $push_notification_opened is never captured, in any app state.

Set it in ios/Runner/AppDelegate.swift:

Swift
import UserNotifications
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UNUserNotificationCenter.current().delegate = self
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

The SDK logs a warning when the delegate is still missing shortly after setup, if you set debug = true on PostHogConfig.

The Android startup fetch doesn't see later token refreshes, so forward those yourself to keep the registered token current:

Dart
if (Platform.isAndroid) {
FirebaseMessaging.instance.onTokenRefresh.listen(
(token) => Posthog().registerPushNotificationToken(token),
);
}

Manual registration

If you manage push tokens yourself, turn the automatic flags off and call the SDK directly:

Dart
// Register the device token. Pass appId to route to a specific channel
// (your Firebase project id, or APNs bundle id on iOS):
await Posthog().registerPushNotificationToken(token, appId: 'your-firebase-project-id');

Call this only after setup() has completed; a token registered earlier is silently dropped.

Unregister the token when a user signs out so it isn't left bound to them:

Dart
await Posthog().unregisterPushNotificationToken();

Calling Posthog().reset() on logout already moves the registered token to the new anonymous identity, so this is only needed when you manage subscriptions yourself.

Registration and unregistration are durable. If the device is offline or the request fails, the SDK retries on the next flush, identity change, or app launch.

Capturing opens

Taps on remote notifications are captured for you. Call the manual API only for opens automatic capture can't see — locally-scheduled notifications, notifications you display yourself from a foreground message, and push delivered outside FCM on Android:

Dart
Posthog().capturePushNotificationOpened(
title: 'Your order shipped',
body: 'Track it in the app',
payload: {'order_id': '1234'},
);

Don't wire this to FirebaseMessaging.onMessageOpenedApp or getInitialMessage(). The SDK already captures those taps, and the manual call isn't deduplicated against them, so the open is counted twice.

The $push_notification_opened event includes $notification_title and $notification_body (plus $notification_subtitle on iOS), and $notification_action for action-button taps. Notification content is only captured for notifications sent by PostHog. Opens of other notifications are still captured, but without title or body.

Opting out

Set capturePushNotificationSubscriptions: false or capturePushNotificationOpened: false on PostHogConfig before setup().

If your app initializes the SDK natively via com.posthog.posthog.AUTO_INIT, the Dart config doesn't apply because the native SDK is already set up before any Dart runs. Opt out with the com.posthog.posthog.CAPTURE_PUSH_NOTIFICATION_SUBSCRIPTIONS and com.posthog.posthog.CAPTURE_PUSH_NOTIFICATION_OPENED keys instead, in Info.plist on iOS and as AndroidManifest <meta-data> on Android.

Identity verification

If your push channel requires identity verification, supply a backend-minted token through pushIdentityProvider:

Dart
config.pushIdentityProvider = (distinctId, appId) async {
// Fetch a freshly-minted token from your backend for this user, then:
return token; // or null to send without one
};

pushIdentityProvider is a Dart callback with no Info.plist or manifest equivalent. If your app uses com.posthog.posthog.AUTO_INIT, set it to false and call Posthog().setup() explicitly, otherwise the provider is never installed.

Troubleshooting

IssueCheck
Token never registersConfirm push is set up in your app and the user granted notification permission. On Android, confirm firebase-messaging is on the classpath (firebase_messaging sets this up). Any manual registerPushNotificationToken call must come after setup() completes.
Push doesn't arriveConfirm the channel's Firebase project (Android) or APNs environment and bundle id (iOS) match your app.
$push_notification_opened never firesOn iOS, confirm your AppDelegate sets UNUserNotificationCenter.current().delegate. On Android, confirm the notification is sent through FCM — detection keys on the google.message_id intent extra — and that your launcher activity still has android:launchMode="singleTop", without which the system resumes the task instead of delivering the tap.
Registration rejected on a Required channelYour pushIdentityProvider isn't returning a valid token in time. See Identity verification.

Still have questions?

Was this page useful?