Push notifications let mobile apps deliver important information and encourage users to return, even when they aren't using the app. Setting them up for the first time can feel complicated.
We'll walk through push notifications in an Expo React Native app, from installing the tools and requesting permission to getting a unique token, sending a notification from a server, and handling it in the app.
Why Use Push Notifications, and How Do They Work? ๐
Notifications can bring users back to your app and deliver information promptly, including new messages, event reminders, and promotions. Here's the overall flow in Expo.
App
โ Your Expo app runs on the user's device.
Get a push token
โ The app asks for notification permission. Once the user grants it, the app obtains a unique
Expo push token
that identifies the device.
Store the token
โ The app sends the token to your backend. Record which user it belongs to so you can send notifications to that user later.
Request delivery
โ To notify one or more users, your server sends their stored tokens and the message to the
Expo Push Service
API.
Expo Push Service
โ Expo receives your request and handles delivery.
Native push services (APNs/FCM)
โ Expo forwards the message to APNs for iOS or FCM for Android.
Device delivery
โ APNs or FCM delivers the notification to the device.
App handling
โ The app can process the notification, display it, or take a specific action.
Expo provides the expo-notifications library to help implement this flow. Let's use it to build the feature.
The expo-notifications library requests permission and obtains the ExpoPushToken used for delivery.
The expo-device package checks whether the app is running on a physical device.
The expo-constants package retrieves projectId from the app configuration.
We'll start writing code here. You can find the complete example at the end.
๐๏ธ
Test on a physical device. The Android and iOS simulators do not work for this example.
You need explicit permission from the user to send notifications. This is an operating system requirement on iOS and Android and helps protect user privacy.
At startup or before a feature needs notifications, check the current permission status. If permission has not been granted, request it using the expo-notifications functions.
import * as Device from 'expo-device';import * as Notifications from 'expo-notifications';import Constants from 'expo-constants';function handleRegistrationError(errorMessage: string) { alert(errorMessage); throw new Error(errorMessage);}async function registerForPushNotificationsAsync() { if (Platform.OS === 'android') { Notifications.setNotificationChannelAsync('default', { name: 'default', importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], lightColor: '#FF231F7C', }); } if (Device.isDevice) { const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { handleRegistrationError('Permission not granted to get push token for push notification!'); return; } const projectId = Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId; if (!projectId) { handleRegistrationError('Project ID not found'); } try { const pushTokenString = ( await Notifications.getExpoPushTokenAsync({ projectId, }) ).data; console.log(pushTokenString); return pushTokenString; } catch (e: unknown) { handleRegistrationError(`${e}`); } } else { handleRegistrationError('Must use physical device for push notifications'); }}
๐จโ๐ป
Replace YOUR_EXPO_PROJECT_ID with your own Expo project ID. Find it in extra.eas.projectId in app.json or in the project settings on the Expo website.
Alternatively, retrieve projectId with the following code.
. Think of it as a unique address that Expo Push Service uses to send notifications to a particular device.
Call Notifications.getExpoPushTokenAsync() to get a token object. Its data property holds the token itself, as shown in the registerForPushNotificationsAsync function above.
Send the token to your backend and store it with the user it belongs to so that you can target that user later.
The token can change when the user reinstalls the app or switches devices. You may need to check it at each startup and update the server when it changes.
Step 4: Receive Push Notifications in the App ๐
Once you can send notifications, decide how the app should receive and process them. The behavior differs depending on whether the app is in the foreground, in the background, or closed.
When a notification arrives while the user is looking at the app, a system alert does not appear by default. Use Notifications.addNotificationReceivedListener to receive the data and handle it in the app, for example by showing an in-app message.
Decide what should happen when the user taps a system notification received while the app was closed or in the background. You might open a specific screen or show relevant data.
Use Notifications.addNotificationResponseReceivedListener. The listener runs when the user interacts with the notification. Its callback receives a response object that exposes the notification through response.notification. Use any extra server data in response.notification.request.content.data to decide what to do next. See the App component example above.
Default Behavior When the App Is Closed or in the Background ๐
When a push notification arrives while the app is closed or in the background, iOS or Android displays it through the system notification interface, such as a banner or the lock screen. Configure presentation details such as sound, badges, and Notification Center visibility at startup with Notifications.setNotificationHandler.
The operating system can restrict running complex logic as soon as a background notification arrives. A common pattern is to handle follow-up work through addNotificationResponseReceivedListener when the user taps the notification.
The examples so far focus on testing in an Expo Go environment. Before building a
standalone app
for store distribution, complete the native push configuration for Android and iOS. Push notifications will fail in the distributed app if this setup is missing. Expo Application Services (EAS) helps manage the process.
In Firebase project settings, add an Android app with the exact package name from android.package in app.json.
Download google-services.json
Download the google-services.json file while adding the app. It contains Firebase configuration information.
Check the FCM server credentials
The Cloud Messaging tab in Firebase project settings is where the older server key was listed. For FCM V1, distinguish the client configuration in google-services.json from the service account credentials used to send notifications.
Upload credentials through EAS CLI
From the project directory, configure FCM credentials using EAS CLI. Use the Google service account key for FCM V1 and keep the client configuration in google-services.json.
Android
โ Google Service Account
โ Manage your Google Service Account for Push Notifications (FCM V1)
โ Set up a Google Service Account Key for Push Notifications (FCM V1)
โ Select the Google account
EAS uses the configured credentials when preparing your app for push notifications.
Check app.json
Make sure android.googleServicesFile points to the correct file.
Choose the app's unique bundle identifier, set ios.bundleIdentifier in app.json, and register it with Apple Developer.
Enable Push Notifications
On the Apple Developer website, enable Push Notifications for the corresponding App ID.
Create an APNs key (.p8 file)
Under Certificates, Identifiers & Profiles > Keys, create a new key with Apple Push Notifications service enabled and download the .p8 file. A certificate (.p12) is another option, but a .p8 key is recommended here. Record the Key ID and Team ID.
Upload credentials through EAS CLI
From your project directory, use EAS CLI to upload the .p8 file and the corresponding Key ID, Team ID, and bundle identifier.
Verify that ios.bundleIdentifier matches the value registered with Apple, and check settings such as ios.usesAppleSignIn. The aps-environment value in ios.entitlements should match the build environment, either development or production. EAS Build often handles this for you.
Native setup can look complicated at first. Follow the Expo documentation and the EAS CLI prompts as you go.
Step 6: Send a Test Notification or Send from Your Backend ๐
Now that you have a token, you can send a push notification.
In production, your backend may need to send a notification when an event occurs, such as a new message or announcement. Send an HTTP POST request to the Expo Push Service API.
Here's a simple Node.js example using fetch.
The expo-notifications library offers more ways to configure notifications.
Android channels
โ On Android 8.0 (Oreo) and later, set up channels so users can manage settings by notification type. Use Notifications.setNotificationChannelAsync() to configure the channel ID, name, importance, and other options. See the registerForPushNotificationsAsync example above.
iOS badge count
โ Read the number on the app icon with Notifications.getBadgeCountAsync() and set it with Notifications.setBadgeCountAsync(). Add logic to synchronize the count with your server.
Scheduled notifications
โ Use Notifications.scheduleNotificationAsync() to schedule a local notification for later or at a recurring interval. This schedules a local notification rather than a remote push.