| name | mobile-push-notifications |
|---|---|
| description | Add push notifications to an Expo app using expo-notifications and EAS Push. Covers permission requests, token registration, local/remote notifications, foreground/background handlers, Android channels, and deep linking from notifications. Use when the user wants push notifications. |
| standards-version | 1.7.0 |
Use this skill when the user:
- Wants to add push notifications to their app
- Needs local or remote notifications
- Asks about notification permissions or Android channels
- Wants to handle notification taps for deep linking
- Mentions "push", "notification", "EAS push", "FCM", "APNs", or "expo-notifications"
- Notification type: local only, remote (push), or both
- Push service: EAS Push (recommended for Expo) or custom (FCM/APNs directly)
- Deep link behavior (optional): which screen to open when the notification is tapped
-
Install expo-notifications.
npx expo install expo-notifications expo-device expo-constants
This requires a development build. Push notifications do not work in Expo Go (SDK 55+ throws an error instead of a warning).
-
Configure app.json. Add the notification plugin:
{ "expo": { "plugins": [ [ "expo-notifications", { "icon": "./assets/notification-icon.png", "color": "#ffffff", "defaultChannel": "default" } ] ], "android": { "useNextNotificationsApi": true } } }Use
mobile_addPushNotificationsto automate this. -
Register for push notifications. Create
lib/notifications.ts:import * as Notifications from "expo-notifications"; import * as Device from "expo-device"; import Constants from "expo-constants"; import { Platform } from "react-native"; Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: true, }), }); export async function registerForPushNotifications(): Promise<string | null> { if (!Device.isDevice) { console.warn("Push notifications require a physical device"); return null; } const { status: existing } = await Notifications.getPermissionsAsync(); let finalStatus = existing; if (existing !== "granted") { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== "granted") { return null; } if (Platform.OS === "android") { await Notifications.setNotificationChannelAsync("default", { name: "Default", importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], }); } const projectId = Constants.expoConfig?.extra?.eas?.projectId; const token = await Notifications.getExpoPushTokenAsync({ projectId }); return token.data; }
-
Handle notifications in the app. In
app/_layout.tsxor a dedicated hook:import { useEffect, useRef } from "react"; import * as Notifications from "expo-notifications"; import { useRouter } from "expo-router"; import { registerForPushNotifications } from "@/lib/notifications"; export function useNotificationHandler() { const router = useRouter(); const responseListener = useRef<Notifications.Subscription>(); useEffect(() => { registerForPushNotifications().then((token) => { if (token) { // Send token to your backend for storage console.log("Push token:", token); } }); // Handle notification tap (app was in background or closed) responseListener.current = Notifications.addNotificationResponseReceivedListener((response) => { const data = response.notification.request.content.data; if (data?.screen) { router.push(data.screen as string); } }); return () => { if (responseListener.current) { Notifications.removeNotificationSubscription( responseListener.current, ); } }; }, []); }
-
Send a push notification via EAS Push. From your backend:
curl -X POST https://exp.host/--/api/v2/push/send \ -H "Content-Type: application/json" \ -d '{ "to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]", "title": "New message", "body": "You have a new message from Alice", "data": { "screen": "/chat/123" } }'
Or from Node.js:
import { Expo } from "expo-server-sdk"; const expo = new Expo(); await expo.sendPushNotificationsAsync([ { to: pushToken, title: "New message", body: "You have a new message from Alice", data: { screen: "/chat/123" }, sound: "default", }, ]);
-
Local notifications. For reminders or timers without a server:
import * as Notifications from "expo-notifications"; async function scheduleLocalNotification() { await Notifications.scheduleNotificationAsync({ content: { title: "Reminder", body: "Time to check your progress!", data: { screen: "/progress" }, }, trigger: { seconds: 60 * 30, repeats: false }, }); }
User: "I want push notifications that open a specific chat screen when tapped."
Agent:
- Installs expo-notifications with
mobile_installDependency - Configures app.json with
mobile_addPushNotifications - Creates
lib/notifications.tswith token registration and handler setup - Implements notification tap handler that routes to
/chat/[id] - Shows how to send a push from the backend with
data: { screen: "/chat/123" } - Sets up an Android notification channel for chat messages
| Step | MCP Tool | Description |
|---|---|---|
| Install packages | mobile_installDependency |
Install expo-notifications, expo-device, expo-constants |
| Configure app.json | mobile_addPushNotifications |
Add notification plugin and Android channel config |
| Add permission | mobile_addPermission |
Add notification permission rationale |
| Configure deep links | mobile_configureDeepLinks |
Set up scheme for notification deep linking |
| Check build | mobile_checkBuildHealth |
Verify project builds with native notification module |
- Testing in Expo Go - Push notifications require a development build. Expo Go does not support them. In SDK 55+,
expo-notificationsthrows an error (not a warning) when used in Expo Go. - iOS/tvOS minimum - SDK 55 raises the minimum deployment target for
expo-notificationsto iOS/tvOS 16.4. Older devices will not receive notifications. - Testing on simulators - Push tokens require a physical device. iOS Simulator does not generate real push tokens.
- Missing Android channel - Android 8+ requires a notification channel. Without one, notifications are silently dropped.
- Not sending token to backend - The push token is generated on the device. You must send it to your server and store it per user for targeted notifications.
- Token expiration - Expo push tokens can change. Re-register on every app launch and update your backend if the token changed.
- Foreground notifications not showing - By default, notifications are hidden when the app is in the foreground. Set
shouldShowAlert: trueinsetNotificationHandler.
- Mobile Permissions - notification permission handling
- Mobile Auth Setup - associate push tokens with authenticated users
- Mobile Navigation Setup - deep linking from notification taps