Expo Push Notifications in 2026: The Complete Setup Guide (SDK 54+)
By Rafat saqqa · 11 min read
A step-by-step 2026 guide to adding Expo push notifications to React Native app development builds, FCM v1, permissions, tokens, and sending your first live push.
Expo Push Notifications in 2026: The Complete Setup Guide (SDK 54+)
Push notifications are how mobile apps earn a second open. Done right, they bring people back; done wrong, they crash on a missing token or silently never arrive. This guide walks through setting up Expo push notifications in a React Native app end to end in 2026 — from installing expo-notifications to sending your first live push to a real device.
A lot changed recently, so if you're following an older tutorial you're probably stuck. Two things trip everyone up in 2026: push notifications no longer work in Expo Go, and Android now requires Firebase Cloud Messaging (FCM) v1 with a service account instead of the old legacy server key. We'll cover both.
TL;DR — how Expo push notifications work
Before the steps, here's the mental model, because it makes every error message make sense:
- Your app asks the user for notification permission and requests an Expo push token (it looks like
ExponentPushToken[…]). - Your app sends that token to your backend and you store it against the user.
- When you want to notify someone, your server sends the token plus a message to the Expo Push Service (
https://exp.host/--/api/v2/push/send). - Expo forwards it to FCM v1 (Android) or APNs (iOS), which delivers it to the device.
You never talk to FCM or APNs directly — Expo is the middleman. Your job is to configure the credentials so Expo is allowed to deliver on your behalf.
Prerequisites
- An Expo project on SDK 54 or newer (
npx expo-doctorwill tell you your version). - An Expo account and the EAS CLI:
npm install -g eas-cli, theneas login. - A physical device — push notifications do not work on simulators or in Expo Go.
- For Android: a Firebase project. For iOS: an Apple Developer account.
Why you need a development build (Expo Go won't work)
This is the single biggest source of "it just doesn't work" reports in 2026. Remote push notifications were deprecated in Expo Go in SDK 52, removed on Android in SDK 53, and are gone entirely as of SDK 54. Expo Go can't bundle every native module, so it dropped push support.
The fix is a development build — a custom version of your app that includes the native notification code. You build it once with EAS and install it on your device, then develop against it exactly like Expo Go. (Local, in-app notifications still work in Expo Go, but that's not what most people mean by push.)
We'll create the development build in Step 5.
Step 1: Install the packages
npx expo install expo-notifications expo-device expo-constants
expo-notifications— the core API for tokens, permissions, and handlers.expo-device— lets you check you're on a real device before requesting a token.expo-constants— used to read your EASprojectId.
Step 2: Add the config plugin
In app.json (or app.config.js), register the plugin so the native notification module is included in your builds:
{
"expo": {
"plugins": [
[
"expo-notifications",
{
"icon": "./assets/notification-icon.png",
"color": "#4F46E5"
}
]
]
}
}
The icon should be a white, transparent PNG — Android uses it as the small status-bar icon.
Step 3: Configure Android (FCM v1)
Google retired the legacy FCM API. Expo now delivers Android notifications through the FCM HTTP v1 API, which authenticates with a Firebase service account JSON file.
- Create (or open) your project in the Firebase console and add an Android app using your package name (e.g.
com.yourcompany.yourapp). - Download the
google-services.jsonfile and reference it inapp.json:
{
"expo": {
"android": {
"googleServicesFile": "./google-services.json",
"package": "com.yourcompany.yourapp"
}
}
}
- In Firebase, go to Project settings → Service accounts → Generate new private key and download the JSON.
- Upload it to EAS so Expo can send on your behalf:
eas credentials
Choose Android → your profile → Google Service Account Key for Push Notifications (FCM V1) and point it at the JSON you downloaded.
Step 4: Configure iOS (APNs)
iOS is simpler because EAS can manage it for you. When you run your first iOS build, EAS offers to generate an APNs key automatically — say yes. If you prefer to do it manually, create an APNs Auth Key in the Apple Developer portal and upload it via eas credentials.
Step 5: Create a development build
Build the custom client that includes native push support and install it on your device:
# Android
eas build --profile development --platform android
# iOS
eas build --profile development --platform ios
When the build finishes, install it on your physical device and start the dev server with npx expo start --dev-client. From here you develop normally.
Step 6: Request permission and get the Expo push token
Create a helper that checks the device, requests permission, sets up an Android channel, and returns the token:
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
export async function registerForPushNotificationsAsync(): Promise<string> {
// Android requires at least one notification channel.
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'Default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#4F46E5',
});
}
if (!Device.isDevice) {
throw new Error('Push notifications require a physical device.');
}
const { status: existing } = await Notifications.getPermissionsAsync();
let finalStatus = existing;
if (existing !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
throw new Error('Permission for push notifications was not granted.');
}
const projectId =
Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId;
const token = (await Notifications.getExpoPushTokenAsync({ projectId })).data;
return token; // e.g. ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]
}
Call it once after the user signs in, then send the returned token to your backend and store it on the user record. That token is what you'll push to later.
Step 7: Handle notifications while the app is open
By default, a notification that arrives while your app is foregrounded is swallowed. Set a handler (once, at module load) to decide how to display it:
import * as Notifications from 'expo-notifications';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldPlaySound: true,
shouldSetBadge: true,
shouldShowBanner: true, // iOS banner while foregrounded
shouldShowList: true, // add to the notification center
}),
});
Note shouldShowBanner and shouldShowList — these replaced the old single shouldShowAlert option. If you copied a pre-SDK-51 tutorial, this is why your handler warns or misbehaves.
Step 8: Send your first test push
The fastest way to verify everything works is Expo's Push Notifications Tool — paste your token, type a message, and send. To do it from a terminal or your backend, hit the Expo Push API directly:
curl -X POST https://exp.host/--/api/v2/push/send \
-H "Content-Type: application/json" \
-d '{
"to": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
"title": "Hello 👋",
"body": "Your first Expo push notification",
"data": { "screen": "/notifications" }
}'
If the device buzzes, you're done. If it doesn't, jump to troubleshooting below.
Step 9: Open the right screen when a notification is tapped
The data payload is where you route deep links. Listen for taps and navigate:
import { useEffect } from 'react';
import * as Notifications from 'expo-notifications';
import { router } from 'expo-router';
export function useNotificationObserver() {
useEffect(() => {
const sub = Notifications.addNotificationResponseReceivedListener((response) => {
const data = response.notification.request.content.data as { screen?: string };
if (data?.screen) router.push(data.screen);
});
return () => sub.remove();
}, []);
}
Sending from your backend (Node)
In production you send from your server, not curl. Batch tokens and post them to the same endpoint:
async function sendPush(tokens: string[], title: string, body: string, data = {}) {
const messages = tokens.map((to) => ({ to, title, body, data, sound: 'default' }));
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(messages),
});
}
Expo accepts up to 100 messages per request and returns receipts you should check to catch invalid or unregistered tokens over time.
Troubleshooting: the errors everyone hits
"It works in the build but not in Expo Go." Correct — that's expected in 2026. Push notifications require a development or production build.
DeviceNotRegistered. The token is stale (the app was uninstalled or the token rotated). Remove it from your database when Expo returns this receipt.
Nothing arrives on Android. Almost always an FCM v1 credential problem: the service account key isn't uploaded to EAS, or google-services.json doesn't match your package name. Re-check Step 3.
Nothing arrives on iOS. Confirm the APNs key is uploaded (eas credentials) and that you accepted the permission prompt. iOS silently drops pushes if permission was denied.
How do I reset the permission prompt while testing? Delete the app from the device (or toggle notifications off then on in system settings) and reinstall — the OS only shows the prompt once per install.
Is Expo push notifications free?
Yes. The Expo Push Service is free to use, including on FCM v1 and APNs, with no per-message charge. You only pay for EAS build minutes if you exceed the free tier when creating your development and production builds — the notifications themselves cost nothing.
Production checklist
- [ ] Store tokens server-side and refresh them on every app launch.
- [ ] Handle
DeviceNotRegisteredreceipts and prune dead tokens. - [ ] Create meaningful Android channels (e.g.
chat,promotions) so users can control them. - [ ] Respect user preferences — let people turn categories off in-app.
- [ ] Test on both a real iPhone and a real Android device before shipping.
Skip the setup — start from a template
Wiring push notifications, permissions, token storage, and deep links from scratch takes a day or two to get right the first time. Every NativeKit Studio template ships with a clean, typed Expo + React Native foundation, and our Extended tier includes a real Node backend you can push from. If you'd rather read the exact steps for your project, see our docs on Expo push notifications, push notifications with FCM, and configuring a Firebase project.
FAQ
Do Expo push notifications work in Expo Go? No. As of SDK 54 they only work in a development or production build. Local in-app notifications still work in Expo Go.
Do I still need Firebase for Android? Yes. Expo delivers Android notifications through FCM v1, which requires a Firebase project and a service account key uploaded to EAS.
Can I send push notifications without a backend? For testing, yes — use Expo's Push Notifications Tool or curl. For production you'll want a backend to store tokens and decide who gets what.
How do I send a push notification to an Android device from a script? POST your ExponentPushToken[...] and message to https://exp.host/--/api/v2/push/send (see Step 8). Expo routes it through FCM v1 to the device.