If you're using EAS Build, you can set your Android notification icon and color tint, add custom push notification sounds, and set your iOS notification environment using the expo-notifications config plugin (what's a config plugin?). To setup, just add the config plugin to the plugins array of your app.json or app.config.js as shown below, then rebuild the app.
The iOS APNS entitlement is always set to 'development'. Xcode automatically changes this to 'production' during archive. Learn more.
On Android, this module requires permission to subscribe to device boot. It's used to setup the scheduled notifications right after the device (re)starts. The RECEIVE_BOOT_COMPLETED permission is added automatically.
Unless you're still running your project in the Expo Go app, Firebase Cloud Messaging is required for all managed and bare workflow Android apps made with Expo. To set up your Expo Android app to get push notifications using your own FCM credentials, follow this guide closely.
If you are not using Expo's push notification service and would instead like to communicate with Apple and Firebase directly, then you should read this guide closely, paying special attention to the payload formats, since providing different formats can result in unexpected behavior on both platforms.
getDevicePushTokenAsync and getExpoPushTokenAsync can sometimes take a long time to resolve on iOS. This is outside of expo-notifications's control, as stated in Apple's “Troubleshooting Push Notifications” technical note:
This is not necessarily an error condition. The system may not have Internet connectivity at all because it is out of range of any cell towers or Wi-Fi access points, or it may be in airplane mode. Instead of treating this as an error, your app should continue normally, disabling only that functionality that relies on push notifications.
As mentioned, the most common reasons for this issue are either an invalid Internet connection (fetching a push token requires an Internet connection to register the device with the service provider) or an invalid configuration of your App ID or Provisioning Profile.
Here are a few ways people claim to have solved this problem, maybe one of these will help you solve it, too!
Make sure 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, as suggested in this SO answer).
Make sure your app configuration is set properly for registering for push notifications (for bare workflow check out this guide, for managed workflow this is done automatically for you by expo-cli) as also suggested by this StackOverflow answer.
If you're in bare workflow you may want to try to debug this even further by logging persistent connection debug information as outlined by this StackOverflow answer.
Try again in a little while
APNS servers near the device may be down as indicated by this forum thread. Take a walk and try again later!
You may need to disable network sharing as this may impact the registration as suggested by this StackOverflow answer.
Restart your device
If you just changed the APNS servers where the app should be registering (by installing a TestFlight build over an Xcode build on the same device) you may need to restart your device as suggested by this StackOverflow answer.
Setup your device with a SIM card
If the device you're experiencing this on hasn't been setup with a SIM card it looks like configuring it may help mitigate this bug as suggested by this StackOverflow answer.
For your notification icon, make sure you follow Google's design guidelines (the icon must be all white with a transparent background) or else it may not be displayed as intended.
In both the managed and bare workflow, you can also set a custom notification color per-notification directly in your NotificationContentInput under the color attribute.
After building your app, the array of files will be available for use in both NotificationContentInput and NotificationChannelInput. You only need to provide the base filename- here's an example using the config above:
awaitNotifications.setNotificationChannelAsync('new-emails',{
name:'E-mail notifications',
sound:'mySoundFile.wav',// Provide ONLY the base filename});awaitNotifications.scheduleNotificationAsync({
content:{
title:"You've got mail! 📬",
sound:'mySoundFile.wav',// Provide ONLY the base filename},
trigger:{
seconds:2,
channelId:'new-emails',},});
You can also manually add notification files to your Android and iOS projects if you prefer:
Manually adding notification sounds on Android
On Androids 8.0+, playing a custom sound for a notification requires more than setting the sound property on the NotificationContentInput. You will also need to configure the NotificationChannel with the appropriate sound, and use it when sending/scheduling the notification.
For the example below to work, you would place your email-sound.wav file in android/app/src/main/res/raw/.
// Prepare the notification channelawaitNotifications.setNotificationChannelAsync('new-emails',{
name:'E-mail notifications',
importance:Notifications.AndroidImportance.HIGH,
sound:'email-sound.wav',// <- for Android 8.0+, see channelId property below});// Eg. schedule the notificationawaitNotifications.scheduleNotificationAsync({
content:{
title:"You've got mail! 📬",
body:'Open the notification to read them all',
sound:'email-sound.wav',// <- for Android below 8.0},
trigger:{
seconds:2,
channelId:'new-emails',// <- for Android 8.0+, see definition above},});
Manually adding notification sounds on iOS
On iOS, all that's needed is to place your sound file in your Xcode project (see the screenshot below), and then specify the sound file in your NotificationContentInput, like this:
awaitNotifications.scheduleNotificationAsync({
content:{
title:"You've got mail! 📬",
body:'Open the notification to read them all',
sound:'notification.wav',},
trigger:{// ...},});
Returns an Expo token that can be used to send a push notification to the device using Expo's push notifications service. Read more in the Push Notifications guide.
This method makes a request to Expo's servers, so it can reject in cases where the request itself fails (like due to the device being offline, experiencing a network timeout, or other HTTPS request failures). To provide offline support to your users, you should try/catch this method and implement retry logic to attempt to get the push token later, once the device is back online.
Note: For Expo's backend to be able to send notifications to your app, you will need to provide it with push notification keys. This can be done using expo-cli (expo credentials:manager). Read more in the “Upload notifications credentials” guide.
This function accepts an optional object allowing you to pass in configuration, consisting of fields (all are optional, but some may have to be defined if configuration cannot be inferred):
experienceId (string) -- Although this is optional, we recommend explicitly passing it in. The ID of the experience to which the token should be attributed. Defaults to Constants.manifest.id exposed by expo-constants. When building with EAS Build, or in the bare workflow, this is required and you must provide a value which takes the shape @username/projectSlug, where username is the Expo account that the project is associated with, and projectSlug is your slug from app.json.
applicationId (string) -- The ID of the application to which the token should be attributed. Defaults to Application.applicationId exposed by expo-application.
development (boolean) -- Makes sense only on iOS, where there are two push notification services: sandbox and production. This defines whether the push token is supposed to be used with the sandbox platform notification service. Defaults to Application.getIosPushNotificationServiceEnvironmentAsync() exposed by expo-application or false. Most probably you won't need to customize that. You may want to customize that if you don't want to install expo-application and still use the sandbox APNS.
A Promise that resolves to an object with the following fields:
type (string) -- Either ios, android or web.
data (string or object) -- Either the push token as a string (for type == "ios" | "android") or an object conforming to the type below (for type == "web"):
In rare situations a push token may be changed by the push notification service while the app is running. When a token is rolled, the old one becomes invalid and sending notifications to it will fail. A push token listener will let you handle this situation gracefully by registering the new token with your backend right away.
Notification events include incoming notifications, interactions your users perform with notifications (this can be tapping on a notification, or interacting with it via notification categories), and rare occasions when your notifications may be dropped.
A few different listeners are exposed, so we've provided a chart below which will hopefully help you understand when you can expect each one to be triggered:
A React hook always returning the notification response that was received most recently (a notification response designates an interaction with a notification, such as tapping on it).
If you don't want to use a hook, you can use Notifications.getLastNotificationResponseAsync() instead.
Responding to a notification tap by opening a URL that could be put into the notification's data (opening the URL is your responsibility and is not a part of the expo-notifications API):
Listeners registered by this method will be called whenever some notifications have been dropped by the server. Applicable only to Firebase Cloud Messaging which we use as notifications service on Android. It corresponds to onDeletedMessages() callback. More information can be found in Firebase docs.
If you'd like to deep link to a specific screen in your app when you receive a push notification, you can configure React Navigation's linking prop to do that:
importReactfrom'react';import{Linking}from'react-native';import*asNotificationsfrom'expo-notifications';import{NavigationContainer}from'@react-navigation/native';exportdefaultfunctionApp(){return(<NavigationContainer
linking={{
config:{// Configuration for linking},asyncgetInitialURL(){// First, you may want to do the default deep link handling// Check if app was opened from a deep linkconst url =awaitLinking.getInitialURL();if(url !=null){return url;}// Handle URL from expo push notificationsconst response =awaitNotifications.getLastNotificationResponseAsync();const url = response?.notification.request.content.data.url;return url;}subscribe(listener){constonReceiveURL=({ url }:{ url: string })=>listener(url);// Listen to incoming links from deep linkingLinking.addEventListener('url', onReceiveURL);// Listen to expo push notificationsconst subscription =Notifications.addNotificationResponseReceivedListener(response=>{const url = response.notification.request.content.data.url;// Any custom logic to see whether the URL needs to be handled//...// Let React Navigation handle the URLlistener(url);});return()=>{// Clean up the event listenersLinking.removeEventListener('url', onReceiveURL);
subscription.remove();};},}}>{/* Your app content */}</NavigationContainer>);}
When a notification is received while the app is running, using this function you can set a callback that will decide whether the notification should be shown to the user or not.
When a notification is received, handleNotification is called with the incoming notification as an argument. The function should respond with a behavior object within 3 seconds, otherwise the notification will be discarded. If the notification is handled successfully, handleSuccess is called with the identifier of the notification, otherwise (or on timeout) handleError will be called.
The default behavior when the handler is not set or does not respond in time is not to show the notification.
The function receives a single argument which should be either null (if you want to clear the handler) or an object of fields:
handleNotification ((Notification) => Promise\<NotificationBehavior>) -- (required) a function accepting an incoming notification returning a Promise resolving to a behavior (NotificationBehavior) applicable to the notification
handleSuccess ((notificationId: string) => void) -- (optional) a function called whenever an incoming notification is handled successfully
handleError ((error: Error) => void) -- (optional) a function called whenever handling of an incoming notification fails
Please note: In order to handle notifications while the app is backgrounded on iOS, you must add remote-notification to the ios.infoPlist.UIBackgroundModes key in your app.json, and add "content-available": 1 to your push notification payload. Under normal circumstances, the “content-available” flag should launch your app if it isn’t running and wasn’t killed by the user, however, this is ultimately decided by the OS so it might not always happen.
When a notification is received while the app is backgrounded, using this function you can set a callback that will be run in response to that notification. Under the hood, this function is run using expo-task-manager. You must define the task first, with TaskManager.defineTask. Make sure you define it in the global scope.
The taskName argument is the string you passed to TaskManager.defineTask as the "taskName". The callback function you define with TaskManager.defineTask will receive an object with the following fields:
data: The remote payload delivered by either FCM (Android) or APNs (iOS). See here for details.
error: The error (if any) that occurred during execution of the task.
executionInfo: JSON object of additional info related to the task, including the taskName.
import*asTaskManagerfrom'expo-task-manager';import*asNotificationsfrom'expo-notifications';constBACKGROUND_NOTIFICATION_TASK='BACKGROUND-NOTIFICATION-TASK';TaskManager.defineTask(BACKGROUND_NOTIFICATION_TASK,({ data, error, executionInfo })=>{console.log('Received a notification in the background!');// Do something with the notification data});Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK);
Calling this function checks current permissions settings related to notifications. It lets you verify whether the app is currently allowed to display alerts, play sounds, etc. There is no user-facing effect of calling this.
Prompts the user for notification permissions according to request. Request defaults to asking the user to allow displaying alerts, setting badge count and playing sounds.
Each option corresponds to a different native platform authorization option (a list of iOS options is available here, on Android all available permissions are granted by default and if a user declines any permission an app can't prompt the user to change).
On iOS, permissions for sending notifications are a little more granular than they are on Android. Because of this, you should rely on the NotificationPermissionsStatus's ios.status field, instead of the root status field. This value will be one of the following, accessible under Notifications.IosAuthorizationStatus:
NOT_DETERMINED: The user hasn't yet made a choice about whether the app is allowed to schedule notifications
DENIED: The app isn't authorized to schedule or receive notifications
AUTHORIZED: The app is authorized to schedule or receive notifications
PROVISIONAL: The application is provisionally authorized to post noninterruptive user notifications
EPHEMERAL: The app is authorized to schedule or receive notifications for a limited amount of time
Sets the badge of the app's icon to the specified number. Setting to 0 clears the badge. On iOS, this method requires that you have requested the user's permission for allowBadge via requestPermissionsAsync, otherwise it will automatically return false.
Note: Not all Android launchers support application badges. If the launcher does not support icon badges, the method will resolve to false.
Note: This method has been deprecated in favor of using an explicit NotificationHandler and the scheduleNotificationAsync method. More info may be found at https://expo.fyi/presenting-notifications-deprecated.
import*asNotificationsfrom'expo-notifications';Notifications.presentNotificationAsync({
title:'Look at that notification',
body:"I'm so proud of myself!",});
import*asNotificationsfrom'expo-notifications';// First, set the handler that will cause the notification// to show the alertNotifications.setNotificationHandler({handleNotification:async()=>({
shouldShowAlert:true,
shouldPlaySound:false,
shouldSetBadge:false,}),});// Second, call the methodNotifications.scheduleNotificationAsync({
content:{
title:'Look at that notification',
body:"I'm so proud of myself!",},
trigger:null,});
Schedules a notification to be triggered in the future.
Note: Please note that this does not mean that the notification will be presented when it is triggered. For the notification to be presented you have to set a notification handler with setNotificationHandler that will return an appropriate notification behavior. For more information see the example below.
It returns a Promise resolving to a string --- a notification identifier you can later use to cancel the notification or to identify an incoming notification.
If the return value is null, the notification won't be triggered. Otherwise, the return value is the Unix timestamp in milliseconds at which the notification will be triggered.
import*asNotificationsfrom'expo-notifications';asyncfunctionlogNextTriggerDate(){try{const nextTriggerDate =awaitNotifications.getNextTriggerDateAsync({
hour:9,
minute:0,});console.log(nextTriggerDate ===null?'No next trigger date':newDate(nextTriggerDate));}catch(e){console.warn(`Couldn't have calculated next trigger date: ${e}`);}}
The first and only argument to the function is the notification identifier, obtained either in setNotificationHandler or in the listener added with addNotificationReceivedListener.
Starting in Android 8.0 (API level 26), all notifications must be assigned to a channel. For each channel, you can set the visual and auditory behavior that is applied to all notifications in that channel. Then, users can change these settings and decide which notification channels from your app should be intrusive or visible at all. (source: developer.android.com)
If you do not specify a notification channel, expo-notifications will create a fallback channel for you, named Miscellaneous. We encourage you to always ensure appropriate channels with informative names are set up for the application and to always send notifications to these channels.
Calling these methods is a no-op for platforms that do not support this feature (iOS, Web and Android below version 8.0 (26)).
A Promise resolving to the channel object (of type NotificationChannel) or to null if there was no channel found for this identifier. On platforms that do not support notification channels, it will always resolve to null.
Assigns the channel configuration to a channel of a specified name (creating it if need be). This method lets you assign given notification channel to a notification channel group.
Note: For some settings to be applied on all Android versions, it may be necessary to duplicate the configuration across both a single notification and it's respective notification channel. For example, for a notification to play a custom sound on Android versions below 8.0, the custom notification sound has to be set on the notification (through the NotificationContentInput), and for the custom sound to play on Android versions above 8.0, the relevant notification channel must have the custom sound configured (through the NotificationChannelInput). For more information, see "Setting custom notification sounds on Android".
A Promise resolving to the object (of type NotificationChannel) describing the modified channel or to null if the platform does not support notification channels.
A Promise resolving to an array of channel groups. On platforms that do not support notification channel groups, it will always resolve to an empty array.
A Promise resolving to the channel group object (of type NotificationChannelGroup) or to null if there was no channel group found for this identifier. On platforms that do not support notification channels, it will always resolve to null.
A Promise resolving to the object (of type NotificationChannelGroup) describing the modified channel group or to null if the platform does not support notification channels.
Notification categories allow you to create interactive push notifications, so that a user can respond directly to the incoming notification either via buttons or a text response. A category defines the set of actions a user can take, and then those actions are applied to a notification by specifying the categoryIdentifier in the NotificationContent.
On iOS, notification categories also allow you to customize your notifications further. With each category, not only can you set interactive actions a user can take, but you can also configure things like the placeholder text to display when the user disables notification previews for your app.
Calling one of the following methods is a no-op on Web.
identifier: A string to associate as the ID of this category. You will pass this string in as the categoryIdentifier in your NotificationContent to associate a notification with this category. Don't use the characters : or - in your category identifier. If you do, categories might not work as expected.
actions: An array of NotificationActions, which describe the actions associated with this category. Each of these actions takes the shape:
identifier: A unique string that identifies this action. If a user takes this action (i.e. selects this button in the system's Notification UI), your app will receive this actionIdentifier via the NotificationResponseReceivedListener.
buttonTitle: The title of the button triggering this action.
textInput: Optional object which, if provided, will result in a button that prompts the user for a text response.
submitButtonTitle: (iOS only) A string which will be used as the title for the button used for submitting the text response.
placeholder: A string that serves as a placeholder until the user begins typing. Defaults to no placeholder string.
options: Optional object of additional configuration options.
opensAppToForeground: Boolean indicating whether triggering this action foregrounds the app (defaults to true). If false and your app is killed (not just backgrounded), NotificationResponseReceived listeners will not be triggered when a user selects this action.
isAuthenticationRequired: (iOS only) Boolean indicating whether triggering the action will require authentication from the user.
isDestructive: (iOS only) Boolean indicating whether the button title will be highlighted a different color (usually red). This usually signifies a destructive action such as deleting data.
options: An optional object of additional configuration options for your category (these are all iOS only):
previewPlaceholder: Customizable placeholder for the notification preview text. This is shown if the user has disabled notification previews for the app. Defaults to the localized iOS system default placeholder (Notification).
intentIdentifiers: Array of Intent Class Identifiers. When a notification is delivered, the presence of an intent identifier lets the system know that the notification is potentially related to the handling of a request made through Siri. Defaults to an empty array.
categorySummaryFormat: A format string for the summary description used when the system groups the category’s notifications.
customDismissAction: A boolean indicating whether to send actions for handling when the notification is dismissed (the user must explicitly dismiss the notification interface- ignoring a notification or flicking away a notification banner does not trigger this action). Defaults to false.
allowInCarPlay: A boolean indicating whether to allow CarPlay to display notifications of this type. Apps must be approved for CarPlay to make use of this feature. Defaults to false.
showTitle: A boolean indicating whether to show the notification's title, even if the user has disabled notification previews for the app. Defaults to false.
showSubtitle: A boolean indicating whether to show the notification's subtitle, even if the user has disabled notification previews for the app. Defaults to false.
allowAnnouncement: A boolean indicating whether to allow notifications to be automatically read by Siri when the user is using AirPods. Defaults to false.
A Promise resolving to an array of NotificationCategorys. On platforms that do not support notification channels, it will always resolve to an empty array.
A Promise resolving to true if the category was successfully deleted, or false if it was not. An example of when this method would return false is if you try to delete a category that doesn't exist.
In simple terms, an object of type: Platform.OS and data: any. The data type depends on the environment -- on a native device it will be a string, which you can then use to send notifications via Firebase Cloud Messaging (Android) or APNS (iOS); on web it will be a registration object (VAPID).
Borrowing from DevicePushToken a little bit, it's an object of type: 'expo' and data: string. You can use the data value to send notifications via Expo Notifications service.
An object representing a request to present a notification. It has content — how it's being represented — and a trigger — what triggers the notification. Many notifications (Notification) may be triggered with the same request (eg. a repeating notification).
exporttypeNotificationContent={// Notification title - the bold text displayed above the rest of the content
title:string|null;// On iOS - subtitle - the bold text displayed between title and the rest of the content// On Android - subText - the display depends on the platform
subtitle:string|null;// Notification body - the main content of the notification
body:string|null;// Data associated with the notification, not displayed
data:{[key:string]:unknown};// Application badge number associated with the notification
badge:number|null;
sound:'default'|'defaultCritical'|'custom'|null;
categoryIdentifier:string|null;}&(|{// iOS-specific additions// See https://developer.apple.com/documentation/usernotifications/unnotificationcontent?language=objc// for more information on specific fields.
launchImageName:string|null;
attachments:{
identifier:string|null;
url:string|null;type:string|null;}[];
summaryArgument?:string|null;
summaryArgumentCount?:number;
threadIdentifier:string|null;
targetContentIdentifier?:string;}|{// Android-specific additions// See https://developer.android.com/reference/android/app/Notification.html#fields// for more information on specific fields.
priority?:AndroidNotificationPriority;
vibrationPattern?:number[];// Format: '#AARRGGBB'
color?:string;});
An object representing notification content that you pass in to presentNotificationAsync or as a part of NotificationRequestInput.
exportinterfaceNotificationContentInput{// Fields corresponding to NotificationContent
title?:string;
subtitle?:string;
body?:string;
data?:{[key:string]:unknown};
badge?:number;
sound?:boolean|string;// Android-specific fields// See https://developer.android.com/reference/android/app/Notification.html#fields// for more information on specific fields.
vibrate?:boolean|number[];
priority?:AndroidNotificationPriority;// Format: '#AARRGGBB', '#RRGGBB' or one of the named colors,// see https://developer.android.com/reference/kotlin/android/graphics/Color?hl=en
color?:string;// If set to false, the notification will not be automatically dismissed when clicked.// The setting used when the value is not provided or is invalid is true (the notification// will be dismissed automatically). Corresponds directly to Android's `setAutoCancel`// behavior. In Firebase terms this property of a notification is called `sticky`.// See:// - https://developer.android.com/reference/android/app/Notification.Builder#setAutoCancel(boolean),// - https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#AndroidNotification.FIELDS.sticky
autoDismiss?:boolean;// If set to true, the notification cannot be dismissed by swipe. This setting defaults// to false if not provided or is invalid. Corresponds directly do Android's `isOngoing` behavior.// See: https://developer.android.com/reference/android/app/Notification.Builder#setOngoing(boolean)
sticky?:boolean;// iOS-specific fields// See https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent?language=objc// for more information on specific fields.
launchImageName?:string;
attachments?:{
url:string;
identifier?:string;
typeHint?:string;
hideThumbnail?:boolean;
thumbnailClipArea?:{ x:number; y:number; width:number; height:number};
thumbnailTime?:number;}[];}
A trigger related to a daily notification. This is an Android-only type, the same functionality will be achieved on iOS with a CalendarNotificationTrigger.
A trigger related to a weekly notification. This is an Android-only type, the same functionality will be achieved on iOS with a CalendarNotificationTrigger.
A trigger related to a yearly notification. This is an Android-only type, the same functionality will be achieved on iOS with a CalendarNotificationTrigger.
A type representing possible triggers with which you can schedule notifications. A null trigger means that the notification should be scheduled for delivery immediately.
A trigger that will cause the notification to be delivered once at the specified Date. If you pass in a number it will be interpreted as a Unix timestamp.
A trigger that will cause the notification to be delivered once or many times when the date components match the specified values. Corresponds to native UNCalendarNotificationTrigger.
Note: This type of trigger is only available on iOS.