iOS Notification Service Extension with Expo push notifications
iOS Notification Service Extension for adding additional content to notifications, such as images, is not formally included in Expo Notifications. However, you can add it using a config plugin with custom native code and configuration.
Sending notifications directly via FCM and APNs with expo-notifications
You can send notifications directly to platform push APIs from your backend while still using expo-notifications to retrieve the native push token and configure notifications separately for each platform. Client-side code remains cross-platform with expo-notifications, but you will need to implement server-side logic to interact with FCM and APNs APIs individually.
Web notifications support in Expo
Expo notifications do not support web notifications. However, some third-party solutions may offer this capability. Consider your app's requirements when choosing a notification service.
expo-notifications library compatibility with notification services
The expo-notifications library is designed and tested to work with Expo's push notification service and notifications sent directly from FCM and APNs. Some advanced features may not be compatible with third-party providers, as they often have their own native and React Native SDKs optimized for their services.
Expo push notifications key capabilities
Expo Notifications provides the following key capabilities: fully compatible with the expo-notifications library, includes an EAS dashboard to track notification delivery to FCM and APNs, and supports testing notifications with the Expo Notifications Tool.
Expo push notifications abstracts FCM and APNs
Expo simplifies implementing push notifications by handling much of the complexity involved in communicating with Firebase Cloud Messaging (FCM) or Apple Push Notification Service (APNs). This allows you to treat Android and iOS notifications in the same way and save time both on the front-end and back-end.
Upload FCM V1 service account key via EAS CLI
To upload a Google Service Account Key for FCM V1 using EAS CLI: run 'eas credentials', select Android > production > Google Service Account, select 'Manage your Google Service Account Key for Push Notifications (FCM V1)', then select 'Set up a Google Service Account Key for Push Notifications (FCM V1)' > 'Upload a new service account key'. The EAS CLI automatically detects JSON files in your project directory and prompts you to select one; press Y to continue.
Add google-services.json to app.json configuration
Download google-services.json from the Firebase Console and place it at the root of your project directory. In app.json, add 'expo.android.googleServicesFile' with the path to google-services.json as its value. This file is required for your Android app to be registered with FCM and contains public-facing identifiers, so it can be committed to your repository.
Upload FCM V1 service account key via expo.dev dashboard
To upload a Google Service Account Key for FCM V1 via the expo.dev dashboard: go to Project settings > Credentials, for Android click 'Add Application Identifier' or select an existing one, under Service Credentials > FCM V1 service account key click 'Add a service account key', then under 'Upload new key' upload your JSON credential file and click Save.
google-services.json API key restrictions for FCM V1
If the API key in google-services.json (the 'client.api_key.current_key' field) is restricted, verify it in the Google Cloud Console under API restrictions. Allow both the FCM Registration API and the Firebase Installations API, or leave the key unrestricted. Under Application restrictions, use the SHA-1 from Release > Setup > App Integrity > App signing key certificate in the Google Play Console (not your upload key). A mismatch causes the Firebase Installations API to return '403 PERMISSION_DENIED: Requests from this Android client application are blocked', and your app will not receive a push token.
Assign Firebase Cloud Messaging API Admin role for existing service account
To use an existing Google Service Account Key for FCM V1: open the IAM Admin page in Google Cloud Console at https://console.cloud.google.com/iam-admin/iam, locate the Principal you want to modify and click the pencil icon to edit it. Click 'Add Role' and select 'Firebase Cloud Messaging API Admin' from the dropdown, then click Save.
Keep service account JSON file out of version control
Add the Google Service Account Key JSON file to your version control's ignore file (for example, .gitignore) to avoid committing it to your repository, since it contains sensitive data.
Create Firebase project for FCM V1 notifications
To set up Android push notifications with FCM V1, first create a new Firebase project for your app in the Firebase Console at https://console.firebase.google.com, or use an existing Firebase project if one already exists.
Generate Google Service Account Key in Firebase Console
In the Firebase console, navigate to Project settings > Service accounts. Click Generate New Private Key, then confirm by clicking Generate Key. Securely store the resulting JSON file containing the private key.
Use FCM or APNs directly instead of Expo service
If you need finer-grained control over notifications, you can communicate directly with FCM and APNs instead of using the Expo push notification service. The expo-notifications API is push-service agnostic, allowing direct integration with Firebase Cloud Messaging and Apple Push Notification service.
Minimal working example for push notifications
Example showing how to register for, send, and receive push notifications in a React Native app using expo-notifications. The example includes: setNotificationHandler for foreground notifications, sendPushNotification function to send to Expo API, registerForPushNotificationsAsync to get token, listeners for notification events, and UI to display token and notification content.
Install expo-notifications and expo-constants libraries
To set up Expo push notifications, install the expo-notifications and expo-constants libraries. The expo-notifications library is used to request user permission and obtain the ExpoPushToken for sending push notifications. The expo-constants library is used to get the projectId value from the app config.
Get Expo push token with projectId
Call Notifications.getExpoPushTokenAsync with a projectId parameter to fetch the Expo push token, which is unique to the device and projectId combination. The projectId can be retrieved from Constants.expoConfig.extra.eas.projectId or Constants.easConfig.projectId.
Configure projectId for push notifications
To attribute the Expo push token to a specific project, set the projectId in your app configuration. One advantage is that the token attribution does not change when a project is transferred between different accounts or the existing account gets renamed. Retrieve projectId using: Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId.
Add notification received listener
Use Notifications.addNotificationReceivedListener to listen for notifications that arrive while the app is foregrounded. This returns an unsubscribe function that should be called in a cleanup function to remove the listener.
Add notification response listener
Use Notifications.addNotificationResponseReceivedListener to listen for user interactions with notifications (taps). This returns an unsubscribe function that should be called in a cleanup function to remove the listener.
Android FCM setup for push notifications
For Android push notifications, configure Firebase Cloud Messaging (FCM) V1 credentials. Follow the FCM V1 credentials guide to set up your credentials before building with EAS.
iOS push notifications require paid developer account
A paid Apple Developer Account is required to generate iOS push notification credentials. Register your iOS device on which you want to test before running eas build for the first time.
iOS push notifications setup during first EAS build
When creating an iOS development build for the first time, the EAS CLI will prompt you to: Setup Push Notifications for your project, and Generate a new Apple Push Notifications service key. Answer yes to enable push notifications.
Test push notifications with Expo tool
After creating and installing a development build, use the Expo push notifications tool at https://expo.dev/notifications to send test notifications. Start the development server, open the development build on your device, enter the ExpoPushToken in the tool with message title and body details, and click Send a Notification button.
Push notification prerequisites for testing
Push notifications can be tested on: a physical Android or iOS device, an Android Emulator with Google Play services, or an iOS Simulator running Xcode 14 or later (macOS 13+, iOS 16+).
Send push notification via Expo API endpoint
To send a push notification, make a POST request to https://exp.host/--/api/v2/push/send with Content-Type application/json. The request body must include: to (the ExpoPushToken), sound (string, e.g. 'default'), title (string), body (string), and data (object for custom data). Headers should include Accept: application/json and Accept-encoding: gzip, deflate.
Android notification channel configuration
For Android, create a notification channel using Notifications.setNotificationChannelAsync before requesting permissions. The channel requires: name (string), importance (AndroidImportance level, e.g. MAX), vibrationPattern (array of numbers), and lightColor (hex color string).
Request push notification permissions
Call Notifications.getPermissionsAsync to check current permission status. If not 'granted', call Notifications.requestPermissionsAsync to prompt the user. Only proceed with push token registration if finalStatus is 'granted'.
Android may not deliver notifications when app is closed due to OS settings
On Android, OS-level settings revolving around performance and battery optimization can prevent notifications from being delivered when the app is closed. For example, the Deep Clear option on OnePlus devices using Android 9 and lower versions can prevent notification delivery.
addNotificationResponseReceivedListener fires on user interaction
The addNotificationResponseReceivedListener event listener is fired whenever a user taps on or interacts with a notification. It works when the app is foregrounded, backgrounded, or killed. It receives a response object as its argument.
Unsubscribe from notification listeners using remove()
Both addNotificationReceivedListener and addNotificationResponseReceivedListener return subscription objects with a remove() method. Call remove() on these subscriptions to stop listening for notifications, typically in a useEffect cleanup function.
Notification listener registration example
Example registering notification listeners in React:
```js
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
const notificationListener = Notifications.addNotificationReceivedListener(notification => {
console.log(notification);
});
const responseListener = Notifications.addNotificationResponseReceivedListener(response => {
console.log(response);
});
return () => {
notificationListener.remove();
responseListener.remove();
};
}, []);
```
Access notification custom data via notification.request.content.data
Custom data passed with a notification is accessible through the notification.request.content.data object. This property contains user-defined key-value pairs that were included when the notification was sent.
Android notification object structure
Android notifications received by addNotificationReceivedListener contain the following structure: notification.request.trigger (containing remoteMessage with originalPriority, sentTime, notification object, and data object), notification.request.content (containing title, body, data, and other presentation properties), notification.request.identifier, and notification.date (timestamp as milliseconds since epoch).
iOS notification object structure
iOS notifications received by addNotificationReceivedListener contain the following structure: notification.request.trigger (containing class, type, and payload with aps and body), notification.request.identifier, notification.request.content (containing title, body, data, badge, sound, and other presentation properties), and notification.date (timestamp as seconds since epoch with fractional seconds).
setNotificationHandler configures foreground notification behavior
Use Notifications.setNotificationHandler with a handleNotification() callback to configure how notifications are presented when the app is foregrounded. The callback returns an object with boolean options: shouldPlaySound, shouldSetBadge, shouldShowBanner, and shouldShowList.
setNotificationHandler example
Example configuring foreground notification behavior:
```jsx
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldPlaySound: false,
shouldSetBadge: false,
shouldShowBanner: true,
shouldShowList: true,
}),
});
```
Expired push notification credentials on iOS
When your push notification credentials have expired on iOS, run eas credentials, choose iOS and a build profile, then remove your push notification key and generate a new one.
Expo push notification service is free
There is no cost associated with sending notifications through Expo push notification service.
Expo push notification service is optional
You can use any push notification service for Expo projects. The getDevicePushTokenAsync method from expo-notifications allows you to get the native device push token, which you can then use with other services, or send your notifications directly through FCM and APNs.
Push notification connections are encrypted
Expo's connections to Apple and Google are encrypted and use HTTPS.
Push notification contents storage
Expo does not store the contents of push notifications any longer than it takes to deliver them to the push notification services operated by Google and Apple. Notifications are stored only in memory and in message queues, not in databases.
Push notification contents visibility
Expo cannot see push notification contents ordinarily. However, if the Expo team is actively debugging the push notifications service, they may see notification contents at a breakpoint.
When ExpoPushToken changes
The ExpoPushToken remains the same across app upgrades and never expires. On Android, reinstalling the app may result in the token changing. On iOS, the token remains the same even after uninstalling and reinstalling the app. The token changes if you change your applicationId or experienceId (usually @expoUsername/projectSlug). If a user uninstalls the app, you will receive a DeviceNotRegistered error from Expo's servers, indicating you should stop sending notifications to that token.
Testing push notifications with curl commands
To send a notification and get the push receipt: first send a notification using curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/send" -d '{"to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]","title":"hello","body": "world"}', then use the resulting ticket id to request the push receipt using curl -H "Content-Type: application/json" -X POST "https://exp.host/--/api/v2/push/getReceipts" -d '{"ids": ["XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"]}'.
Push notifications in SDK 53 and later require development builds
In SDK 53 and later, Expo Go does not support push notifications functionality. To test push notifications, you must use a development build. In SDK 52 and earlier, Expo Go used Expo's credentials, which allowed push notifications to work in development without setting up your own credentials.
Credentials required for production push notifications
When you build your app for the app stores, you need to generate and use your own credentials. On Android, follow the FCM credentials guide. On iOS, this is handled by your push key. Revoking the push key associated with your app results in your notifications failing to be delivered. To fix that, add a new push key with eas credentials.
Android notification priority levels
Expo accepts four priority levels for notifications sent to Android: 'default' (manually mapped to the default priority documented by Apple and Google), 'high' (mapped to the high priority level documented by Apple and Google), 'normal' (mapped to the normal priority level documented by Apple and Google), and priority omitted (treated exactly as if 'default' were specified). Setting the priority to 'high' gives your notification the greatest likelihood that Android will display the notification.
No valid aps-environment entitlement string found error
This error occurs if you haven't set up a push notification key for your iOS project. To fix this, check your Project Credentials page at https://expo.dev/accounts/[account]/projects/[project]/credentials/ios. To generate a new push notification key, trigger a new build by running eas build --profile [profile] --platform ios.
Slow getDevicePushTokenAsync and getExpoPushTokenAsync on iOS
getDevicePushTokenAsync and getExpoPushTokenAsync can sometimes take a long time to resolve on iOS. This is outside of expo-notifications's control and is documented in Apple's Troubleshooting Push Notifications technical note. The system may not have Internet connectivity because it is out of range of cell towers or Wi-Fi access points, or it may be in airplane mode. Your app should continue normally, disabling only that functionality that relies on push notifications.
Push notification token fetch troubleshooting on iOS
To troubleshoot slow push token fetching on iOS: ensure the device has a reliable connection to the Internet (try turning off Wi-Fi or switching to another network, and disabling firewall block on port 5223); for existing React Native projects, manually enable the Push Notifications capability in Xcode; try again later as APNS servers near the device may be down; disable network sharing on your device; restart your device if you just changed the APNS servers (by installing a TestFlight build over an Xcode build on the same device); or setup your device with a SIM card if it hasn't been configured with one. Also refer to Apple's Technical Note on troubleshooting push notifications.
Android notification icon requirements
The notification icon on Android must be all white with a transparent background. This is required and enforced by Google, not Expo. If the notification icon appears as a gray or white square, this indicates an issue with the image asset you're providing.
APNs HTTP/2 POST request structure
APNs POST request via HTTP/2 requires method POST, apns-topic header with bundle identifier, path /3/device/{nativeDeviceToken}, and authorization bearer token. Message body contains aps object with alert (title and body) and optional experienceId/scopeKey fields (only required for legacy Expo Go SDK 52 and earlier):
```js
const http2 = require('http2');
const client = http2.connect(
IS_PRODUCTION ? 'https://api.push.apple.com' : 'https://api.sandbox.push.apple.com'
);
const request = client.request({
':method': 'POST',
':scheme': 'https',
'apns-topic': 'YOUR-BUNDLE-IDENTIFIER',
':path': '/3/device/' + nativeDeviceToken,
authorization: `bearer ${authorizationToken}`,
});
request.setEncoding('utf8');
request.write(
JSON.stringify({
aps: {
alert: {
title: "\uD83D\uDCE7 You've got mail!",
body: 'Hello world! \uD83C\uDF10',
},
},
experienceId: '@yourExpoUsername/yourProjectSlug',
scopeKey: '@yourExpoUsername/yourProjectSlug',
})
);
request.end();
```
FCM notification payload fields supported by expo-notifications
FCM supported fields are listed in Firebase's notification payload documentation. For Android, expo-notifications supports the fields documented in the FirebaseRemoteMessage reference.
FCMv1 OAuth 2.0 access token generation example
To send FCMv1 notifications, obtain an OAuth 2.0 access token using google-auth-library with your FCM private key:
```ts
import { JWT } from 'google-auth-library';
function getAccessTokenAsync(
key: string // Contents of your FCM private key file
) {
return new Promise(function (resolve, reject) {
const jwtClient = new JWT(
key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/cloud-platform'],
null
);
jwtClient.authorize(function (err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
```
Push notifications removed from Expo Go in SDK 53+
From SDK 53 onwards, push notification support is removed from Expo Go. The experienceId and scopeKey fields in notifications are only applicable when testing in legacy Expo Go (SDK 52 and earlier).
FCMv1 notification POST request example
Send FCMv1 notifications via POST to `https://fcm.googleapis.com/v1/projects/{FCM_PROJECT_NAME}/messages:send`. Include Authorization header with Bearer token, and message body with token, data fields including channelId, message, title, body, scopeKey, and experienceId (experienceId and scopeKey required for Expo Go in SDK 52 and earlier):
```ts
async function sendFCMv1Notification() {
const key = require(process.env.FCM_SERVER_KEY);
const firebaseAccessToken = await getAccessTokenAsync(key);
const fcmToken = process.env.FCM_DEVICE_TOKEN;
const messageBody = {
message: {
token: fcmToken,
data: {
channelId: 'default',
message: 'Testing',
title: `This is an FCM notification message`,
body: JSON.stringify({ title: 'bodyTitle', body: 'bodyBody' }),
scopeKey: '@yourExpoUsername/yourProjectSlug',
experienceId: '@yourExpoUsername/yourProjectSlug',
},
},
};
const response = await fetch(
`https://fcm.googleapis.com/v1/projects/${process.env.FCM_PROJECT_NAME}/messages:send`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${firebaseAccessToken}`,
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(messageBody),
}
);
const readResponse = (response: Response) => response.json();
const json = await readResponse(response);
console.log(`Response JSON: ${JSON.stringify(json, null, 2)}`);
}
```
APNs entitlement required for push notifications
Receiving push notifications on iOS requires the APNs entitlement. For apps using Continuous Native Generation (CNG), either add the expo-notifications library with its plugin to the plugins array in app.json, or manually add the aps-environment entitlement to ios.entitlements in app.json. For non-CNG apps, add the entitlement in Xcode.
APNs aps-environment entitlement configuration
If not using expo-notifications library, manually add APNs entitlement to app.json:
```json
{
"expo": {
"ios": {
"entitlements": {
"aps-environment": "development"
}
}
}
}
```