Expo Push
Push and local notifications through Expo's push service.
The rule
This is the whole text, exactly as your agent receives it. Nothing is held back for the paid tier.
Permission
- Ask in context, after explaining the benefit — never on first launch. On iOS the system prompt appears once per install; a decline is effectively permanent, and you cannot ask again.
- Show your own explanation screen first and only call
requestPermissionsAsync()when the user agrees. - Denied is a normal state, not an error. The app must work fully without push.
Tokens
- Refresh on every launch — tokens rotate on reinstall and device restore.
- Send to the backend with the user id; delete on sign-out, or the next user on that device receives the previous user's notifications.
- Purge tokens that come back
DeviceNotRegisteredin a receipt.
Handling
- Register the response listener once, at startup.
- Handle the cold start with
getLastNotificationResponseAsync(). A tap usually launches the app from scratch, and without this the deep link is lost. - Android needs an explicit notification channel or importance is ignored.
Sending
- A 200 from the push API means accepted, not delivered. Check receipts.
- Never put personal data or a token in a notification payload — it is visible on the lock screen and passes through Apple's and Google's infrastructure.
Never
- Never expect push to work in a simulator or in Expo Go.
- Never block a feature on notification permission.
6 formats, one per tool
Each tab is the file that tool actually reads, at the path it actually looks in. Knowing where each one looks is most of the work of supporting it.
---
description: Expo push notification conventions
globs: ["src/services/notifications/**", "src/features/**"]
alwaysApply: false
---
# Expo Push
## Permission
- Ask **in context**, after explaining the benefit — never on first launch.
On iOS the system prompt appears once per install; a decline is effectively
permanent, and you cannot ask again.
- Show your own explanation screen first and only call
`requestPermissionsAsync()` when the user agrees.
- Denied is a normal state, not an error. The app must work fully without push.
## Tokens
- Refresh on every launch — tokens rotate on reinstall and device restore.
- Send to the backend with the user id; **delete on sign-out**, or the next user
on that device receives the previous user's notifications.
- Purge tokens that come back `DeviceNotRegistered` in a receipt.
## Handling
- Register the response listener once, at startup.
- Handle the **cold start** with `getLastNotificationResponseAsync()`. A tap
usually launches the app from scratch, and without this the deep link is lost.
- Android needs an explicit notification channel or importance is ignored.
## Sending
- A 200 from the push API means accepted, not delivered. Check receipts.
- Never put personal data or a token in a notification payload — it is visible
on the lock screen and passes through Apple's and Google's infrastructure.
## Never
- Never expect push to work in a simulator or in Expo Go.
- Never block a feature on notification permission.
Hand-written by the module author, frontmatter and all. It is the source the four derived formats are rendered from, so a correction lands here first.
What else this module writes
The rule is one file of several. Selecting Expo Push contributes all of this too — merged with every other module you pick, with conflicts resolved rather than duplicated.
EXPO_PUBLIC_EAS_PROJECT_IDrequiredEAS project id, needed when requesting a push token. Also present in `app.json`.EXPO_ACCESS_TOKENoptionalAuthenticates sends against the Expo push API. Server-side only.Advisory history
Every time this rule turned out to be wrong, and what we did about it.
This rule has been accurate since it was published. That is a fact about the rule, not a promise about the future — which is the whole reason this section exists.
Pro tells you the day a correction lands that affects a repo you actually have.
See what Pro adds →Rules people add alongside this one
The wizard picks the rest of the stack with you, writes all 6 formats, and leaves a manifest so check can tell you when any of it drifts.