Install the native-notify package in an Expo app, register for push and send a test notification.
Not an Expo app?
Flutter, bare React Native, Capacitor, Swift and Kotlin apps use Push notifications — native APNs and FCM tokens, no SDK.
Already on Classic?
To move an installed Classic app to the current service without an app update, see Migrate a Classic app.
Prerequisites
- A Native Notify account for your app id and app token.
- A free Expo account.
- Node.js with
npm. - Expo in your app, managed or bare. You don't need to eject. To add it to an existing React Native app, see Installing Expo modules.
1. Install in your project
npm install native-notify
npx expo install expo-device expo-notifications expo-constants
expo-constants is a required peer dependency — it resolves your EAS projectId. Development builds and EAS builds also need the expo-notifications config plugin in app.json:
{
"expo": {
"plugins": ["expo-notifications"]
}
}
Expo Go on Android
Expo Go on Android (SDK 53+) can't get push tokens. Use a development build: npx expo run:android or an EAS development build. The notification inbox still works in Expo Go with mode="indie".
2. Register for push in your app's code
Call registerNNPushToken at the top of your root function component:
import registerNNPushToken from 'native-notify';
export default function App() {
registerNNPushToken(yourAppId, 'yourAppToken');
return (
// ...
);
}
A class component needs a function wrapper: see wrap-class-in-hook.
Set your credentials once
Skip the ids on every call with a provider, or init() at module scope:
import { NativeNotifyProvider } from 'native-notify';
export default function App() {
return (
<NativeNotifyProvider appId={yourAppId} appToken="yourAppToken">
<Root />
</NativeNotifyProvider>
);
}
import { NativeNotify } from 'native-notify';
NativeNotify.init({ appId: yourAppId, appToken: 'yourAppToken' });
Every function still accepts explicit ids. Hooks and components (registerNNPushToken, the inbox components) read <NativeNotifyProvider> first, then NativeNotify.init(). Plain functions (registerIndieID, the follow, inbox and send functions, analytics reports) read NativeNotify.init() only. In a component, useNativeNotify() returns the active config.
Callbacks and token rotation
registerNNPushToken(yourAppId, 'yourAppToken', {
onRegistered: (result) => console.log('native-notify ready:', result.expoPushToken),
onError: (error) => console.warn('native-notify registration failed:', error),
});
onRegistered(result)— the token was registered with Native Notify.onError(error)— registration, or a token-rotation re-registration, failed.watchTokenRotation— defaulttrue. Re-registers when the device token rotates (Android reinstall or applicationId change, iOS backup restore).
Registration retries once and times out after 10 seconds. To run the token flow yourself, call registerForPushNotificationsAsync(). It never throws:
import { registerForPushNotificationsAsync } from 'native-notify';
const result = await registerForPushNotificationsAsync();
if (result.status === 'success') {
// result.expoPushToken, result.devicePushToken, result.expoAndroidToken, ...
} else {
console.warn(result.status, result.reason); // 'skipped' or 'error', and why
}
3. Send a test push with the API
Your app needs an EAS projectId:
npm install -g eas-cli
eas login
eas init
eas init writes extra.eas.projectId into app.json. Native Notify reads Constants?.expoConfig?.extra?.eas?.projectId ?? Constants?.easConfig?.projectId. If neither exists, registration fails with "no EAS projectId found". See Expo's projectId docs.
Start the app and open it on a device:
npx expo start
- iOS: a physical iPhone (Expo Go works), or an iOS Simulator on Xcode 14+.
- Android: a physical phone, or an emulator with Google Play services — in a development build, not Expo Go.
Then send yourself a push:
POST https://app.nativenotify.com/api/notification
{
"appId": 12345,
"appToken": "yourAppToken",
"title": "Hello",
"body": "My first push notification",
"dateSent": "9-24-2026 3:05PM"
}
- Send JSON with
Content-Type: application/json. axios sets it for you;fetchand server languages like Python must set it, or the server can't readappIdandappTokenand answers401. - Expo limits a message to 4 KiB — title, body,
pushDataand rich fields together. A larger one is refused with400andnotification payload exceeds Expo's 4KiB limit.
To check the device side without the API, paste its Expo push token (it starts with ExponentPushToken[) into expo.dev/notifications.