# Expo Recipe (Universal Push)

Two ways to push an Expo app with Native Notify — the native-notify SDK on the Expo path, or universal push with the native APNs/FCM token from expo-notifications.

Expo apps have two supported routes:

1. **The Expo SDK path — start here.** Install `native-notify` and call `registerNNPushToken()`; the SDK registers Expo push tokens, and sends go through Expo's push service with receipts, inboxes and analytics. Follow [Installation and Setup](/docs/setup/installation).
2. **Universal push** — register the *native* APNs/FCM token yourself. Choose this when you want sends to go directly through your own Firebase project and Apple key (no Expo push service in the middle).

## Universal push in an Expo app

Get the native device token with `expo-notifications`:

```js
import * as Notifications from "expo-notifications";
import { Platform } from "react-native";

export async function registerUniversalDevice({ appId, appToken, subscriberId }) {
  const { status } = await Notifications.requestPermissionsAsync();
  if (status !== "granted") return null;

  // The NATIVE token: APNs on iOS, FCM on Android. Development/EAS builds
  // only — Expo Go cannot mint native tokens (Android, SDK 53+).
  const devicePushToken = await Notifications.getDevicePushTokenAsync();

  // A key that survives re-launches. On iOS use expo-application's
  // getIosIdForVendorAsync(), on Android getAndroidId(), or any UUID you
  // persist in secure storage.
  const deviceId = await stableDeviceId();

  const tokens =
    Platform.OS === "ios"
      ? { apnsToken: String(devicePushToken.data) }
      : { fcmToken: String(devicePushToken.data) };

  const res = await fetch("https://app.nativenotify.com/api/universal/device/register", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      appId,
      appToken,
      deviceId,
      platform: Platform.OS,
      subscriberId, // omit for a mass (all-users) device
      tokens,
    }),
  });
  return res.json(); // { ok, device, tokens }
}
```

That is the entire integration: the same call registers on every launch (idempotent), the same `deviceId` re-registers a rotated token, and the app's [push credentials](/docs/universal-push/credentials) are what make sends deliver.

> **Expo Go:**
>
> `getDevicePushTokenAsync()` needs a development or EAS build. Expo Go cannot mint push tokens on Android (SDK 53+) and never exposes native tokens on iOS.

## Which route should we choose?

| You want…                                                                                                | Route                                                         |
| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| The fastest Expo setup, Notification Inbox components, receipts, analytics app-opens                     | **Expo SDK path** — `native-notify` + `registerNNPushToken()` |
| Sends straight through your own Firebase/Apple credentials, one contract shared with Flutter/native apps | **Universal push** — this page                                |
| Both features in different apps                                                                          | Both — the paths are independent per app                      |

## Next steps

- [Device Registration](/docs/universal-push/registration) — every field and rule.
- [Verify Delivery](/docs/universal-push/verification) — test-send to the device you just registered.
- [Send Notifications](/docs/universal-push/sending) — blasts, device lists, subscriber lists.
