Expo Notifications
Build with Expo Notifications for React Native push notification delivery. Covers device token registration, local and remote notifications, notification handlers, and the Expo Push API for server-side sending.
You are a mobile notification engineer who integrates Expo Notifications into React Native projects. Expo Notifications provides a unified API across iOS and Android for local scheduling and remote push delivery. It abstracts platform-specific permission flows, token formats, and notification channels behind a single JavaScript API. The Expo Push Service acts as a relay, accepting a unified payload and routing to APNs and FCM. ## Key Points - **Testing on simulator**: Push tokens cannot be obtained on iOS Simulator or Android Emulator; always test on physical devices. - **Ignoring receipts**: Expo returns ticket IDs that must be checked later for delivery errors; ignoring them lets stale tokens accumulate. - **Requesting permissions immediately**: Prompting on first launch without context leads to denials; pre-explain the value first. - **Hardcoding project ID**: Use `Constants.expoConfig` to read the project ID dynamically rather than hardcoding it. - React Native apps built with the Expo managed or bare workflow - Cross-platform mobile push without direct FCM/APNs server integration - Apps that need both local scheduled and remote push notifications - Teams that want a single push API endpoint instead of managing two platforms - Rapid prototyping of notification features in Expo projects ## Quick Example ```bash npx expo install expo-notifications expo-device expo-constants ``` ```env EXPO_ACCESS_TOKEN=your_expo_access_token ```
skilldb get notification-services-skills/expo-notificationsFull skill: 195 linesExpo Notifications
You are a mobile notification engineer who integrates Expo Notifications into React Native projects. Expo Notifications provides a unified API across iOS and Android for local scheduling and remote push delivery. It abstracts platform-specific permission flows, token formats, and notification channels behind a single JavaScript API. The Expo Push Service acts as a relay, accepting a unified payload and routing to APNs and FCM.
Core Philosophy
Unified Token Management
Expo issues its own push token (ExpoPushToken) that wraps the underlying platform token. Your server sends to Expo's push service using this token, avoiding direct FCM/APNs integration. Always fetch the token on every app launch since it can change, and persist it to your backend with the associated user ID.
Permission-First Design
iOS requires explicit notification permission; Android 13+ does too. Always check and request permissions before attempting to get a push token. Design your UX to explain why notifications matter before triggering the system prompt, as a denied prompt cannot be re-triggered without the user visiting Settings.
Foreground Handling Matters
By default, notifications received while the app is in the foreground are silently consumed. You must configure setNotificationHandler to decide whether to show alerts, play sounds, or set badges when the app is active. Ignoring this leads to reports of "notifications not working" from users who are actively using the app.
Setup
Install
npx expo install expo-notifications expo-device expo-constants
Environment Variables
EXPO_ACCESS_TOKEN=your_expo_access_token
Key Patterns
1. Register for Push Notifications
Do:
import * as Notifications from "expo-notifications";
import * as Device from "expo-device";
import Constants from "expo-constants";
async function registerForPush(): Promise<string | null> {
if (!Device.isDevice) 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;
const projectId = Constants.expoConfig?.extra?.eas?.projectId;
const token = await Notifications.getExpoPushTokenAsync({ projectId });
return token.data;
}
Not this:
// Skipping permission check and device validation
const token = await Notifications.getExpoPushTokenAsync();
// Crashes on simulator, fails silently without permissions
2. Configure Foreground Handling
Do:
Notifications.setNotificationHandler({
handleNotification: async (notification) => {
const data = notification.request.content.data;
const showInForeground = data.showWhenActive !== false;
return {
shouldShowAlert: showInForeground,
shouldPlaySound: showInForeground,
shouldSetBadge: true,
};
},
});
Not this:
// Forgetting to set handler — notifications silently swallowed in foreground
// No call to setNotificationHandler at all
3. Server-Side Sending via Expo Push API
Do:
import { Expo, ExpoPushMessage } from "expo-server-sdk";
const expo = new Expo();
async function sendPush(tokens: string[], title: string, body: string) {
const messages: ExpoPushMessage[] = tokens
.filter((t) => Expo.isExpoPushToken(t))
.map((to) => ({ to, title, body, sound: "default" as const }));
const chunks = expo.chunkPushNotifications(messages);
for (const chunk of chunks) {
const receipts = await expo.sendPushNotificationsAsync(chunk);
// Store ticket IDs for later receipt checking
}
}
Not this:
// Sending one-by-one without chunking or token validation
for (const token of tokens) {
await fetch("https://exp.host/--/api/v2/push/send", {
method: "POST",
body: JSON.stringify({ to: token, title, body }),
});
}
Common Patterns
Handling Notification Taps
import { useEffect } from "react";
import * as Notifications from "expo-notifications";
import { router } from "expo-router";
function useNotificationNavigation() {
useEffect(() => {
const subscription = Notifications.addNotificationResponseReceivedListener(
(response) => {
const data = response.notification.request.content.data;
if (data.screen) {
router.push(data.screen as string);
}
}
);
return () => subscription.remove();
}, []);
}
Scheduling Local Notifications
await Notifications.scheduleNotificationAsync({
content: { title: "Reminder", body: "Your task is due in 15 minutes" },
trigger: { seconds: 900, type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL },
});
Android Channel Configuration
await Notifications.setNotificationChannelAsync("orders", {
name: "Order Updates",
importance: Notifications.AndroidImportance.HIGH,
sound: "default",
vibrationPattern: [0, 250, 250, 250],
});
Checking Receipts for Delivery Errors
const receiptChunks = expo.chunkPushNotificationReceiptIds(ticketIds);
for (const chunk of receiptChunks) {
const receipts = await expo.getPushNotificationReceiptsAsync(chunk);
for (const [id, receipt] of Object.entries(receipts)) {
if (receipt.status === "error") {
if (receipt.details?.error === "DeviceNotRegistered") {
await removeToken(id);
}
}
}
}
Anti-Patterns
- Testing on simulator: Push tokens cannot be obtained on iOS Simulator or Android Emulator; always test on physical devices.
- Ignoring receipts: Expo returns ticket IDs that must be checked later for delivery errors; ignoring them lets stale tokens accumulate.
- Requesting permissions immediately: Prompting on first launch without context leads to denials; pre-explain the value first.
- Hardcoding project ID: Use
Constants.expoConfigto read the project ID dynamically rather than hardcoding it.
When to Use
- React Native apps built with the Expo managed or bare workflow
- Cross-platform mobile push without direct FCM/APNs server integration
- Apps that need both local scheduled and remote push notifications
- Teams that want a single push API endpoint instead of managing two platforms
- Rapid prototyping of notification features in Expo projects
Install this skill directly: skilldb add notification-services-skills
Related Skills
Firebase Cloud Messaging
Integrate Firebase Cloud Messaging (FCM) for cross-platform push notifications. Covers token management, topic subscriptions, data vs notification messages, and server-side sending via the Admin SDK.
Knock
Implement Knock notification infrastructure for multi-channel delivery. Covers workflow orchestration, in-app feeds, email, push, SMS, and Slack channels with preference management and batching.
Magicbell
Build with MagicBell for embeddable notification inboxes and multi-channel delivery. Covers the notification inbox UI component, real-time WebSocket updates, server-side sending, user preferences, and category management.
Notifee
Integrate Notifee for robust local notification management within your React Native applications. This service provides a comprehensive cross-platform API for displaying, interacting with, and managing notifications, enhancing user engagement without relying on remote push services.
Ntfy
Implement ntfy.sh for simple HTTP-based pub/sub push notifications. Covers publishing messages via HTTP PUT/POST, subscribing via SSE and WebSocket, authentication, attachments, and self-hosted server configuration.
Pushbullet
Integrate Pushbullet to send real-time notifications, links, and files across all your devices and to other Pushbullet users. It's ideal for personal alerts, devops monitoring, IoT device notifications, or simple user-to-user communication.