Developers · API v1.0.0
React Native (Bare / CLI) Integration
This guide is for (non-Expo) React Native projects created with npx @react-native-community/cli init. For the general API reference, see the Overview.
Supported versions: React Native 0.74+ (including the New Architecture) · iOS 13.4+ · Android 7.0+ (API 24) · Node 18+
Because NotiPilot 1.0.0 delivers notifications through the Expo Push Service, each device needs an Expo Push Token. In a bare React Native project, there are two ways to get one:
| Method | When to use it |
|---|---|
A. expo-notifications (recommended) |
For most projects. Expo modules are added to the bare project; no migration to Expo is required. |
B. @react-native-firebase/messaging + token conversion |
If the project already uses Firebase Messaging. |
With either method, you need to create an EAS project (npx eas-cli init) and upload your FCM V1 / APNs credentials with eas credentials. The EAS project ID is the value you enter in the Expo Project ID field of the NotiPilot dashboard and send to the API as app_id.
Method A — expo-notifications (recommended)
1. Installation
npx install-expo-modules@latest
npx expo install expo-notifications expo-device expo-secure-store expo-localization expo-crypto expo-application
cd ios && pod install && cd ..
Android
- Download
google-services.jsonfrom the Firebase console and place it inandroid/app/. android/build.gradle→dependencies { classpath 'com.google.gms:google-services:4.4.2' }android/app/build.gradle→ addapply plugin: 'com.google.gms.google-services'at the bottom
iOS (Xcode → Signing & Capabilities)
- Add the Push Notifications capability.
- Under Background Modes, check Remote notifications.
2. Client code
You can use the src/notipilot.ts file from the Expo guide as is. Since a bare project has no expo-constants manifest, just hard-code the project ID and read app_version from expo-application:
import * as Application from 'expo-application';
// Same as the "Expo Project ID" in the NotiPilot dashboard
const PROJECT_ID = 'YOUR-EAS-PROJECT-ID';
// attributes inside registerDevice():
// app_version: Application.nativeApplicationVersion,
Method B — Firebase Messaging + Expo token conversion
If @react-native-firebase/messaging is already installed in your project, you can convert the device's native token into an Expo token and send it to NotiPilot.
npm i @react-native-firebase/app @react-native-firebase/messaging react-native-get-random-values uuid @react-native-async-storage/async-storage react-native-localize react-native-device-info
import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import messaging from '@react-native-firebase/messaging';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as RNLocalize from 'react-native-localize';
import DeviceInfo from 'react-native-device-info';
import { Platform, PermissionsAndroid } from 'react-native';
const NOTIPILOT_BASE_URL = 'https://app.notipilot.com/api/v1';
const PROJECT_ID = 'YOUR-EAS-PROJECT-ID';
async function getDeviceUid() {
let uid = await AsyncStorage.getItem('notipilot_device_uid');
if (!uid) {
uid = uuidv4();
await AsyncStorage.setItem('notipilot_device_uid', uid);
}
return uid;
}
async function requestPermission(): Promise<boolean> {
if (Platform.OS === 'android' && Platform.Version >= 33) {
const r = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
if (r !== PermissionsAndroid.RESULTS.GRANTED) return false;
}
const status = await messaging().requestPermission();
return (
status === messaging.AuthorizationStatus.AUTHORIZED ||
status === messaging.AuthorizationStatus.PROVISIONAL
);
}
/** Converts the native FCM/APNs token into an Expo Push Token. */
async function toExpoPushToken(deviceUid: string): Promise<string> {
const isIOS = Platform.OS === 'ios';
const deviceToken = isIOS ? await messaging().getAPNSToken() : await messaging().getToken();
if (!deviceToken) throw new Error('Could not get native push token');
const res = await fetch('https://exp.host/--/api/v2/push/getExpoPushToken', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: isIOS ? 'apns' : 'fcm',
deviceId: deviceUid.toLowerCase(),
development: __DEV__ && isIOS, // iOS debug builds use the APNs sandbox
appId: DeviceInfo.getBundleId(), // Android package / iOS bundle id
deviceToken,
projectId: PROJECT_ID,
}),
});
if (!res.ok) throw new Error(`Expo token conversion failed: ${res.status}`);
const json = await res.json();
return json.data.expoPushToken; // "ExponentPushToken[...]"
}
export async function registerDevice(extra: Record<string, string | number | boolean | null> = {}) {
if (!(await requestPermission())) return null;
const deviceUid = await getDeviceUid();
const token = await toExpoPushToken(deviceUid);
const locale = RNLocalize.getLocales()[0];
const res = await fetch(`${NOTIPILOT_BASE_URL}/register-device`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
app_id: PROJECT_ID,
device_uid: deviceUid,
token,
platform: Platform.OS,
provider: 'expo',
attributes: {
locale: locale?.languageCode ?? null,
country: RNLocalize.getCountry(),
app_version: DeviceInfo.getVersion(),
...extra,
},
}),
});
return res.json();
}
// Re-register when the token is refreshed
messaging().onTokenRefresh(() => registerDevice().catch(() => {}));
exp.host/--/api/v2/push/getExpoPushTokenis the endpoint used internally by Expo's ownexpo-notificationslibrary; Expo does not document it separately. Method A is the safest long-term option.
For the identify-device call and error handling, you can reuse the identify, logout, and post functions from the Expo guide as is.
Checklist
- EAS project created, and its project ID entered as the Expo Project ID in the dashboard
- FCM V1 + APNs credentials uploaded with
eas credentials -
POST_NOTIFICATIONSpermission requested on Android 13+ - Push Notifications + Remote notifications capabilities enabled on iOS
-
countryand, if possible,citysent as attributes