Registering for push

Permissions + token capture.

7 minmedium

Push notifications go: phone gets a token from Apple / Google → mobile sends the token to your API → API saves it on the user → later, a job worker sends pushes to that token via Expo Push. This lesson covers the first two steps.

The plumbing

Terminal
$pnpm add expo-notifications expo-device

Already in the Grit mobile scaffold.

Request permission + grab the token

apps/mobile/lib/push.ts
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 registerForPush(): Promise<string | null> {
if (!Device.isDevice) return null // simulator can't receive real pushes
// iOS-specific: must request permission
const { status: existing } = await Notifications.getPermissionsAsync()
let final = existing
if (existing !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
final = status
}
if (final !== 'granted') return null // user denied
// Android-specific: must create a channel
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.DEFAULT,
})
}
const projectId = Constants.expoConfig?.extra?.eas?.projectId
const token = (await Notifications.getExpoPushTokenAsync({ projectId })).data
return token // ExponentPushToken[xxxxxxxxxxxxxxxxxxxx]
}

Returns an Expo Push token — a string starting with ExponentPushToken[. Expo's push service uses it to identify the device when you want to send a push.

Save it to your API

apps/mobile/hooks/use-auth.ts (after login)
async function login(email: string, password: string) {
// ... existing login logic ...
// After successful login, register for push and save the token
const pushToken = await registerForPush()
if (pushToken) {
await api.post('/api/users/me/push-token', { push_token: pushToken })
}
}

On the API side, add a handler that updates the user's push_token column.

Foreground vs background — what the user sees

  • App in foreground — Expo doesn't show a system notification by default. Use Notifications.setNotificationHandler to decide whether to show a banner.
  • App backgrounded / killed — system shows the notification automatically. Tapping it deep-links into the app.
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
})
Simulator gotcha: Device.isDevice is false in the iOS simulator and Android emulator, so push registration is skipped. You must test push on a physical device.

Handling taps + deep-links

Notifications.addNotificationResponseReceivedListener((response) => {
const data = response.notification.request.content.data
if (data?.orderId) {
router.push(`/orders/${data.orderId}`)
}
})

The data payload you sent from the API arrives here. Use it to navigate deep into the app — "new message" opens the thread, "order shipped" opens the order.

Updating the token over time

Expo push tokens can change — after app reinstall, after iOS re-permission cycle. Re-register on every cold start so your API always has the current one:

useEffect(() => {
if (isAuthenticated) {
registerForPush().then((token) => {
if (token) api.post('/api/users/me/push-token', { push_token: token })
})
}
}, [isAuthenticated])

Quick check

You're testing push notifications on your iOS simulator. registerForPush() returns null every time. What's wrong?

Try it

Register your device for push:

  1. On a physical phone (not the simulator), run the app via Expo Go.
  2. Sign in.
  3. Call registerForPush() on mount (or log in). Console-log the returned token.
  4. The token starts with ExponentPushToken[ — copy it.
  5. Paste the token in notes.md.

What's next

Last lesson of the chapter — actually send a push from your Grit API to your phone.

Spot a typo? Have an idea?

Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.

Suggest an improvement on GitHub