Expo Push (Classic)

Expo setup

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

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 — default true. 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; fetch and server languages like Python must set it, or the server can't read appId and appToken and answers 401.
  • Expo limits a message to 4 KiB — title, body, pushData and rich fields together. A larger one is refused with 400 and notification 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.

Next steps