Push notifications are one of the highest-impact features you can add to a mobile app — and one of the most-searched Expo topics. expo-notifications gives you a single API for both local notifications (scheduled on the device) and remote push notifications (sent from your server). This guide walks through both.
Install
npx expo install expo-notifications expo-device expo-constants
expo-device lets you check for a physical device (required for push), and expo-constants gives you the project ID you need to fetch a token.
Configure the notification handler
Set a handler once, near your app's entry point, so notifications display even when the app is open:
import * as Notifications from "expo-notifications"
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
})
Request permission
Always ask before scheduling or registering:
async function requestPermission() {
const { status } = await Notifications.requestPermissionsAsync()
return status === "granted"
}
Schedule a local notification
Local notifications need no server — perfect for reminders, timers, and streaks:
await Notifications.scheduleNotificationAsync({
content: {
title: "Time to stretch 🧘",
body: "You've been coding for an hour.",
},
trigger: { seconds: 60 * 60, repeats: true },
})
Triggers can be a delay ({ seconds }), a calendar date, or a daily time.
Get an Expo push token
To receive remote notifications, register the device and send its token to your backend:
import * as Notifications from "expo-notifications"
import * as Device from "expo-device"
import Constants from "expo-constants"
async function registerForPush() {
if (!Device.isDevice) return // must be a physical device
const { status } = await Notifications.requestPermissionsAsync()
if (status !== "granted") return
const projectId = Constants.expoConfig?.extra?.eas?.projectId
const token = await Notifications.getExpoPushTokenAsync({ projectId })
return token.data // "ExponentPushToken[xxxxxxxx]"
}
Send that token to your server and store it against the user.
Set up an Android channel
Android requires a channel for notifications to appear. Create one at startup:
import { Platform } from "react-native"
if (Platform.OS === "android") {
await Notifications.setNotificationChannelAsync("default", {
name: "Default",
importance: Notifications.AndroidImportance.DEFAULT,
})
}
Handle taps and incoming notifications
Listen for notifications received while the app runs, and for the user tapping one:
import { useEffect } from "react"
useEffect(() => {
const received = Notifications.addNotificationReceivedListener((n) => {
console.log("received:", n.request.content.title)
})
const responded = Notifications.addNotificationResponseReceivedListener((r) => {
// e.g. navigate based on r.notification.request.content.data
console.log("tapped:", r.notification.request.content.data)
})
return () => {
received.remove()
responded.remove()
}
}, [])
Send a test push
Once you have a token, hit the Expo Push API:
curl -X POST https://exp.host/--/api/v2/push/send \
-H "Content-Type: application/json" \
-d '{ "to": "ExponentPushToken[xxxx]", "title": "Hello", "body": "First push 🎉" }'
Gotchas
- Physical device required for remote push — emulators can't get a token.
- Add the
expo-notificationsconfig plugin (and an icon) in app.json for a production build. - Store the token server-side; it can change, so refresh it on app launch.
Next steps
Notifications usually go hand-in-hand with a backend and auth. Read how to add authentication to an Expo app, and see the full list in the top 10 Expo SDK modules.