# Push Notifications

The easiest way to add push notifications is Firebase. Use [React Native Firebase](https://rnfirebase.io/) and [Notifee](https://notifee.app/) for customization and interaction events.

## Best Practices

- Ensure users are channel members before expecting pushes.
- Register and refresh device tokens after `connectUser`.
- Store and rotate push tokens to avoid duplicates and stale registrations.
- Test foreground, background, and killed states on both platforms.
- Keep push payload templates aligned with in-app navigation needs.

## Prerequisites

Before starting, read the [Push Notification documentation](/chat/docs/sdk/react-native/v8/guides/push-notifications/) and the [push delivery rules](/chat/docs/sdk/react-native/v8/guides/push-notifications/#push-delivery-rules).

<admonition type="note">

Push notifications require membership. Watching a channel isn't enough.

</admonition>

## Firebase Project Setup

Set up a Firebase project:

1. [Create a Firebase project for your apps](https://console.firebase.google.com/).
2. Register your [Android](https://firebase.google.com/docs/android/setup#register-app) app and [iOS](https://firebase.google.com/docs/ios/setup#register-app) app with the Firebase project.
3. Upload your [APNs authentication key](https://firebase.google.com/docs/cloud-messaging/ios/client#upload_your_apns_authentication_key) to your Firebase project.

## App setup

Set up your apps with Firebase:

1. Install React Native Firebase and its messaging module

   ```sh
   # Install and set up the app module
   yarn add @react-native-firebase/app

   # Install the messaging module
   yarn add @react-native-firebase/messaging
   ```

2. [Setup your Android app with credentials and configure Firebase](https://rnfirebase.io/#2-android-setup).
3. [Setup your iOS app with credentials and configure Firebase](https://rnfirebase.io/#3-ios-setup).
4. Add the [Push Notifications Capability](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app) for your iOS app.

### Expo

<admonition type="note">

If you use the Bare workflow, follow the steps above.

</admonition>

React Native Firebase isn’t available in [Expo Go](https://expo.dev/client). Use [expo-dev-client](https://docs.expo.dev/development/getting-started/) and [config plugins](https://docs.expo.dev/guides/config-plugins/). See the [Expo install docs](https://rnfirebase.io/#installation-for-expo-projects).

## Get Google Service Account Credentials

Firebase projects support Google service accounts. Stream uses these credentials to call Firebase server APIs.

These credentials are the [private key file](https://firebase.google.com/docs/admin/setup#:~:text=To%20generate%20a%20private%20key%20file%20for%20your%20service%20account%3A) for your service account in the Firebase console. To generate a private key file for your service account:

- In the Firebase console, open `Settings > Service Accounts`.
- Click `Generate New Private Key`, then confirm by clicking `Generate Key`.
- Securely store the JSON file containing the key.

Upload this JSON file to Stream as described below.

## Upload Firebase Credentials

You can upload the JSON via the dashboard or the app settings API.

### Using Dashboard

1. Go to Chat Overview page on Stream Dashboard

![Chat Overview Page](@chat-sdk/react-native/v8/_assets/guides/push-notifications/chat_overview_page.png)

2. Enable Firebase Notification toggle on Chat Overview

![Firebase Notifications Toggle](@chat-sdk/react-native/v8/_assets/guides/push-notifications/firebase_notifications_toggle.png)

3. Enter your Firebase Credentials in input box below the toggle and **press the "Save" or "Create" button** (depending on whether you're updating an existing push configuration or adding a new one)

### Using API

You can also enable Firebase notifications and upload credentials via API, as shown [here](/chat/docs/sdk/react-native/v8/guides/push-notifications/#updating-firebase-apn-template).

## Registering a device with Stream

After configuring Firebase in the dashboard, register the device with Stream using the Firebase device token.

<admonition type="warning">

`client.addDevice` requires a user token, so call it after `client.connectUser`.

</admonition>

```js
// Request Push Notification permission from device.
const requestPermission = async () => {
  // Request permission for Android 13 and above
  if (Platform.OS === 'android' && Platform.Version >= 33) {
    await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
    return;
  }
  const authStatus = await messaging().requestPermission();
  const enabled =
    authStatus === messaging.AuthorizationStatus.AUTHORIZED || authStatus === messaging.AuthorizationStatus.PROVISIONAL;

  if (enabled) {
    console.log('Authorization status:', authStatus);
  }
};

const App = () => {
  const [isReady, setIsReady] = useState(false);
  const unsubscribeTokenRefreshListenerRef = useRef<() => void>();

  useEffect(() => {
    // Register FCM token with stream chat server.
    const registerPushToken = async () => {
      if(!client) {
        return;
      }
      // unsubscribe any previous listener
      unsubscribeTokenRefreshListenerRef.current?.();
      const token = await messaging().getToken();
      const push_provider = 'firebase';
      const push_provider_name = 'MyRNAppFirebasePush'; // name an alias for your push provider (optional)
      client.addDevice(token, push_provider, USER_ID, push_provider_name);
      await AsyncStorage.setItem('@current_push_token', token);

      const removeOldToken = async () => {
        const oldToken = await AsyncStorage.getItem('@current_push_token');
        if (oldToken !== null) {
          await client.removeDevice(oldToken);
          await AsyncStorage.removeItem('@current_push_token');
        }
      };

      unsubscribeTokenRefreshListenerRef.current = messaging().onTokenRefresh(async newToken => {
        await Promise.all([
          removeOldToken(),
          client.addDevice(newToken, push_provider, USER_ID, push_provider_name),
          AsyncStorage.setItem('@current_push_token', newToken),
        ]);
      });
    };

    const init = async () => {
      await requestPermission();
      await client.connectUser({ id: USER_ID }, USER_TOKEN);
      await registerPushToken();

      setIsReady(true);
    };

    init();

    return async () => {
      await client?.disconnectUser();
      unsubscribeTokenRefreshListenerRef.current?.();
    };
  }, []);

  if (!isReady) {
    return null;
  }

  return (
    <View style={styles.container}>
      <Chat client={client}>{/* Child components of Chat go here */}</Chat>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});
```

## The Push Notification Message Payload

When a message meets the [requirements](#requirements), Stream sends a notification to Firebase. Delivery timing depends on [battery and device state](https://firebase.google.com/docs/cloud-messaging/concept-options#lifetime).

The payload depends on your [push payload template](/chat/docs/react/push_template/) configured in the Dashboard. The `notification` field is handled by Firebase to display the message; `data` is for your app. The [device state and payload contents](https://rnfirebase.io/messaging/usage#notifications) determine whether a notification is shown. By default, iOS includes `notification` and `data`, while Android includes only `data`. Example iOS payload:

```json
{
  "data": {
    "sender": "stream.chat",
    "type": "message.new",
    "version": "v2",
    "id": "d152f6c1-8c8c-476d-bfd6-59c15c20548a",
    "channel_type": "messaging",
    "channel_id": "company-chat",
    "cid": "messaging:company-chat"
  },
  // the notification field is present only on iOS by default and not on Android
  "notification": {
    "title": "Message from user",
    "body": "Hello"
  }
  // other fields..
}
```

<admonition type="note">

If you migrated from push v1 to v2, the v1 template is copied into the v2 `notification` field, so Firebase may also display Android notifications automatically.

</admonition>

## Receiving Notifications When On Background Or Quit State

### iOS

On iOS, the `notification` field is handled by Firebase and shown when the app is backgrounded or quit.

### Android

On Android, use [`setBackgroundMessageHandler`](https://rnfirebase.io/messaging/usage#background--quit-state-messages) to handle background notifications.

<admonition type="warning">

This handler is Android-only. For iOS, you must send [`data`-only payloads](#make-ios-payload-data-only).

</admonition>

Register the handler early (outside app logic), then display the notification in the handler:

```tsx
// index.js
import { AppRegistry } from "react-native";
import messaging from "@react-native-firebase/messaging";
import { StreamChat } from "stream-chat";

export const extractNotificationConfig = (
  remoteMessage: FirebaseMessagingTypes.RemoteMessage,
) => {
  const { stream, ...rest } = remoteMessage.data ?? {};
  const data = {
    ...rest,
    ...((stream as unknown as Record<string, string> | undefined) ?? {}), // extract and merge stream object if present
  };
  const notification = remoteMessage.notification ?? {};
  const body = (data.body ?? notification.body ?? "") as string;
  const title = (data.title ?? notification.title) as string;

  return { data, body, title };
};

messaging().setBackgroundMessageHandler(async (remoteMessage) => {
  // create the android channel to send the notification to
  const channelId = await notifee.createChannel({
    id: "chat-messages",
    name: "Chat Messages",
  });

  // display the notification
  const { data, body, title } = extractNotificationConfig(remoteMessage);
  await notifee.displayNotification({
    title,
    body,
    data,
    android: {
      channelId,
      // add a press action to open the app on press
      pressAction: {
        id: "default",
      },
    },
  });
});

function App() {
  // Your application
}

AppRegistry.registerComponent("app", App);
```

### Listen To User Interactions For Background Notifications

Pressing a notification opens the app by default. If you want to deep-link, detect whether the app was opened from a notification. iOS and Android use different handlers.

On iOS, the [React Native Firebase API](https://rnfirebase.io/messaging/notifications#handling-interaction) provides:

- `messaging().getInitialNotification`: When the application is opened from a quit state.
- `messaging().onNotificationOpenedApp`: When the application is running, but in the background.

On Android, the [Notifee library](https://notifee.app/react-native/v8/docs/events) provides:

- `notifee.getInitialNotification`: When the application is opened from a quit state.
- `notifee.onBackgroundEvent`: When the application is running but in the background.

Handle these during app setup. Example with [React Navigation](https://reactnavigation.org/):

```js
import React, { useEffect, useState } from 'react';
import messaging from '@react-native-firebase/messaging';
import notifee, { EventType } from '@notifee/react-native';
import { NavigationContainer, NavigationContainerRef } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();
const navigationContainerRef = React.createRef<NavigationContainerRef>();

notifee.onBackgroundEvent(async ({ detail, type }) => {
  if (type === EventType.PRESS) {
    // user press on notification detected while app was on background on Android
    const channelId = detail.notification?.data?.channel_id;
    if (channelId) {
       navigationContainerRef.current?.navigate('ChannelScreen', { channelId });
    }
    await Promise.resolve();
  }
});

function App() {
  const [initialChannelId, setInitialChannelId] = useState<string>();
  useEffect(() => {
    const unsubscribeOnNotificationOpen = messaging().onNotificationOpenedApp((remoteMessage) => {
      // Notification caused app to open from background state on iOS
      const channelId = remoteMessage.data?.channel_id;
      // The navigation logic, to navigate to relevant channel screen.
      if (channelId) {
        navigationContainerRef.current?.navigate('ChannelScreen', { channelId });
      }
    });

    notifee.getInitialNotification().then(initialNotification => {
      if (initialNotification) {
        // Notification caused app to open from quit state on Android
        const channelId = initialNotification.notification.data?.channel_id;
        // Start the app with the relevant channel screen.
        setInitialChannelId(channelId);
      }
    });

    messaging()
      .getInitialNotification()
      .then(remoteMessage => {
        if (remoteMessage) {
          // Notification caused app to open from quit state on iOS
          const channelId = remoteMessage.data?.channel_id;
          // Start the app with the relevant channel screen.
          setInitialChannelId(channelId);
        }
    });

    return () => {
      unsubscribeOnNotificationOpen();
    };
  }, []);

  return (
    <NavigationContainer ref={navigationContainerRef}>
      <Stack.Navigator initialRouteName={initialChannelId ? "ChannelScreen" : "Home"}>
        <Stack.Screen name='Home' component={HomeScreen} />
        <Stack.Screen name='ChannelListScreen' component={ChannelListScreen} />
        <Stack.Screen name='ChannelScreen'
          component={ChannelScreen}
          initialParams={initialChannelId ? { channelId: initialChannelId } : undefined}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
```

## Customizing The Delivered Payload

See the [JavaScript SDK documentation](/chat/docs/javascript/push_template/) for available variables and template syntax. Common customizations:

<admonition type="warning">

The methods to customize the push template are **server-side** only.

</admonition>

### Add Notification Field In Android Payload

To include a `notification` payload on Android, add keys to `notification_template`:

```js
const client = StreamChat.getInstance("api_key", "api_secret");
const notification_template = `{
    "title": "New message from {{ sender.name }}",
    "body": "{{ truncate message.text 2000 }}",
    "click_action": "OPEN_ACTIVITY_1",
    "sound": "default"
}`;

client.updateAppSettings({
  firebase_config: {
    notification_template,
  },
});
```

This aligns Android with iOS by using the same payload, so you may not need Notifee. The `click_action` key must be added to your launcher activity in `AndroidManifest.xml`:

```xml
<activity
  android:name=".MainActivity"
  android:label="@string/app_name"
  android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
  android:launchMode="singleTask"
  android:windowSoftInputMode="adjustResize"
  android:exported="true">
  <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
  <intent-filter>
      <action android:name="OPEN_ACTIVITY_1" />
      <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>
```

Also handle the Android background press with Notifee’s `ACTION_PRESS`:

```js
notifee.onBackgroundEvent(async ({ detail, type }) => {
  if (type === EventType.ACTION_PRESS) {
    // user press on notification detected while app was on background on Android
    const channelId = detail.notification?.data?.channel_id;
    if (channelId) {
      navigationContainerRef.current?.navigate("ChannelScreen", { channelId });
    }
    await Promise.resolve();
  }
});
```

<admonition type="info">

`notification_template` is a JSON object with Android-specific push keys. See [AndroidNotification](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#AndroidNotification) for supported fields.

</admonition>

<admonition type="warning">

We don’t recommend this: adding `notification_template` can show notifications in the foreground on Android, while iOS won’t. Most chat apps suppress foreground notifications. If you want them on both platforms, see [display-notification-in-foreground](#display-notification-in-foreground).

</admonition>

### Show badge number on the iOS app

To show unread count on the app badge, add `badge` to `apn_template`:

```js
const client = StreamChat.getInstance("api_key", "api_secret");

const apn_template = `{
  "aps" : {
    "alert": {
      "title": "{{ sender.name }} @ {{ channel.name }}",
      "body": "{{ truncate message.text 2000 }}"
    },
    "badge": {{ unread_count }},
    "mutable-content": 1,
    "category": "stream.chat"
  },
}`;

client.updateAppSettings({
  firebase_config: {
    apn_template,
  },
});
```

Clear the badge when the app opens:

```js
function App() {
  useEffect(() => {
    const clearBadge = async () => {
      await notifee.setBadgeCount(0);
    };
    clearBadge();
  }, []);
  // Rest of your application
}
```

### Make iOS Payload Data Only

If the iOS payload is data-only, use `setBackgroundMessageHandler` and Notifee to display notifications. Example:

```js
const client = StreamChat.getInstance("api_key", "api_secret");

const apn_template = `{
  "aps": {
    "content-available": 1
  }
}`;

client.updateAppSettings({
  firebase_config: {
    apn_template,
  },
});
```

<admonition type="info">

`apn_template` is a JSON object with iOS push keys. See [Payload Key Reference](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification) for supported fields.

</admonition>

<admonition type="warning">

Ensure the headless prop is injected as described [here](https://rnfirebase.io/messaging/usage#background-application-state=), and enable Remote Notifications as described [here](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app).

</admonition>

The `content-available` key marks a [silent background update](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification). iOS treats these as [low priority](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app), so delivery may be throttled.

## Display Notification In Foreground

Firebase displays `notification` on iOS when the app is backgrounded. On Android, use [`setBackgroundMessageHandler`](https://rnfirebase.io/messaging/usage#background--quit-state-messages) for background notifications.

If you want foreground notifications, listen with [`onMessage`](https://rnfirebase.io/messaging/usage#foreground-state-messages) and display a notification. This handler can access React state.

<admonition type="note">

Most chat apps don’t show foreground push notifications, so you may skip this.

</admonition>

```tsx
import messaging from "@react-native-firebase/messaging";
import notifee from "@notifee/react-native";

useEffect(() => {
  // add listener to notifications received when on foreground
  const unsubscribeOnMessage = messaging().onMessage(async (remoteMessage) => {
    const message = await chatClient.getMessage(remoteMessage.data.id);

    // create the android channel to send the notification to
    const channelId = await notifee.createChannel({
      id: "chat-messages",
      name: "Chat Messages",
    });

    // display the notification
    const { stream, ...rest } = remoteMessage.data ?? {};
    const data = {
      ...rest,
      ...((stream as unknown as Record<string, string> | undefined) ?? {}), // extract and merge stream object if present
    };
    await notifee.displayNotification({
      title: "New message from " + message.message.user.name,
      body: message.message.text,
      data,
      android: {
        channelId,
        pressAction: {
          id: "default",
        },
      },
    });
  });

  // add listener to user interactions on foreground notifications
  const unsubscribeForegroundEvent = notifee.onForegroundEvent(
    ({ detail, type }) => {
      if (type === EventType.PRESS) {
        // user has pressed notification
        const channelId = detail.notification?.data?.channel_id;
        // The navigation logic, to navigate to relevant channel screen.
        if (channelId) {
          navigationContainerRef.current?.navigate("ChannelScreen", {
            channelId,
          });
        }
      }
    },
  );

  return () => {
    unsubscribeOnMessage();
    unsubscribeForegroundEvent();
  };
}, []);
```

## Troubleshooting

- If you have enabled offline support and have installed the `@op-engineering/op-sqlite` library, please refer to [this troubleshooting guide](/chat/docs/sdk/react-native/v8/basics/troubleshooting/#using-op-engineeringop-sqlite-with-use_frameworks-linkage--static)
- You can check the "Webhook & Push Logs" section in the Dashboard to see if Notifications were sent by Stream. Please see the [Common Issues & FAQ](/chat/docs/javascript/push_-_common_issues_and_faq/) to diagnose the various cases in which the Stream Chat API may not send the notifications.
- Android devices can restrict your app while it's in the background to preserve battery life. This can prevent trigger notifications from being displayed. The [Notifee documentation](https://notifee.app/react-native/v8/docs/android/background-restrictions) explains it in detail.
- Additionally, suppose Stream Chat API has sent the notifications but was not displayed on the device. In that case, you can check the diagnostics steps for [Android](https://firebase.google.com/support/troubleshooter/fcm/delivery/diagnose/android) and [iOS](https://firebase.google.com/support/troubleshooter/fcm/delivery/diagnose/ios) in the Firebase cloud messaging documentation.
- If you are still having trouble with Push Notifications, please contact `support@getstream.io`.


---

This page was last updated at 2026-04-17T17:33:45.517Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v8/guides/push-notifications/](https://getstream.io/chat/docs/sdk/react-native/v8/guides/push-notifications/).