expo-notifications
Permisos, canales, manejo en primer plano y registro de token push - la guía expo-notifications para apps de Expo SDK 57 que necesitan alertas locales y push remoto.
Busca en todas las páginas de la documentación
Permisos, canales, manejo en primer plano y registro de token push - la guía expo-notifications para apps de Expo SDK 57 que necesitan alertas locales y push remoto.
Tarjeta de referencia rápida - lista para copiar y pegar.
npx expo install expo-notifications expo-device expo-constants// src/notifications/setup.ts - importar en la entrada de la app
import * as Notifications from "expo-notifications";
import { Platform } from "react-native";
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export async function ensureAndroidChannel() {
if (Platform.OS !== "android") return;
await Notifications.setNotificationChannelAsync("default", {
name: "Default",
importance: Notifications.AndroidImportance.DEFAULT,
});
}
export async function requestNotificationPermission(): Promise<boolean> {
const { status: existing } = await Notifications.getPermissionsAsync();
if (existing === "granted") return true;
const { status } = await Notifications.requestPermissionsAsync();
return status === "granted";
}// src/notifications/pushToken.ts
import Constants from "expo-constants";
import * as Device from "expo-device";
import * as Notifications from "expo-notifications";
import { Platform } from "react-native";
export async function getExpoPushToken(): Promise<string | null> {
if (!Device.isDevice) return null;
const granted = await requestNotificationPermission();
if (!granted) return null;
const projectId =
Constants.expoConfig?.extra?.eas?.projectId ??
Constants.easConfig?.projectId;
if (!projectId) {
throw new Error("EAS projectId missing - run npx eas init");
}
const token = await Notifications.getExpoPushTokenAsync({ projectId });
return token.data;
}Cuándo usarlo:
Cuándo evitarlo:
Puerta de permiso, canal Android, registro de token y listener de toque para navegar.
npx expo install expo-notifications expo-device expo-constants// app/(tabs)/settings/notifications.tsx
import { useState } from "react";
import { Pressable, Text, View } from "react-native";
import {
ensureAndroidChannel,
requestNotificationPermission,
} from "@/notifications/setup";
import { registerPushToken } from "@/notifications/registerPushToken";
export default function NotificationSettingsScreen() {
const [status, setStatus] = useState<"idle" | "granted" | "denied">("idle");
async function enableNotifications() {
await ensureAndroidChannel();
const granted = await requestNotificationPermission();
if (!granted) {
setStatus("denied");
return;
}
await registerPushToken();
setStatus("granted");
}
return (
<View style={{ padding: 24, gap: 12 }}>
<Text>Recibe alertas cuando los conductores llegan al sitio.</Text>
<Pressable onPress={enableNotifications}>
<Text style={{ color: "#2563eb", fontWeight: "600" }}>
Habilitar notificaciones
</Text>
</Pressable>
{status === "denied" && (
<Text>Abre Configuración para permitir notificaciones para esta app.</Text>
)}
</View>
);
}// src/notifications/registerPushToken.ts
import { getExpoPushToken } from "./pushToken";
export async function registerPushToken() {
const token = await getExpoPushToken();
if (!token) return;
await fetch(`${process.env.EXPO_PUBLIC_API_URL}/devices/push-token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, platform: "expo" }),
});
}// app/_layout.tsx
import { useEffect } from "react";
import * as Notifications from "expo-notifications";
import { router, Stack } from "expo-router";
import "@/notifications/setup";
export default function RootLayout() {
useEffect(() => {
const sub = Notifications.addNotificationResponseReceivedListener(
(response) => {
const orderId = response.notification.request.content.data?.orderId;
if (typeof orderId === "string") {
router.push(`/orders/${orderId}`);
}
}
);
return () => sub.remove();
}, []);
return <Stack />;
}Lo que esto demuestra:
getExpoPushTokenAsync.data.orderId.| Paso | API | Notas |
|---|---|---|
| Verificar actual | getPermissionsAsync() | undetermined al primer lanzamiento |
| Explicar valor | Pantalla en la app | Requerido para la narrativa de App Review |
| Solicitar | requestPermissionsAsync() | iOS muestra el cuadro de diálogo del sistema una vez por instalación |
| Android 13+ | Lo mismo + canal | Permiso de tiempo de ejecución POST_NOTIFICATIONS |
HIGH solo para alertas urgentes.scheduleNotificationAsync o visualización remota.| Estado | Comportamiento | Configuración |
|---|---|---|
| Primer plano | Banner controlado por setNotificationHandler | shouldShowBanner: true |
| Fondo | El SO muestra según la configuración del canal | El manejador predeterminado todavía se aplica a datos únicamente en Android |
| Cerrado | Tocar abre la app; usa getLastNotificationResponseAsync | Navegación de inicio en frío |
Instalación -> getExpoPushTokenAsync -> POST /devices
Reinstalación -> nuevo token -> upsert fila del servidor
Logout -> BORRAR token del servidor + opcional anular registro
Cambio de cuenta -> registrar nuevo token bajo nuevo userIddeviceId, no solo userId.getExpoPushTokenAsync - las apps de producción a menudo puentean a FCM/APNs directamente a través de credenciales de EAS.| API | Propósito |
|---|---|
setNotificationHandler | Presentación en primer plano |
setNotificationChannelAsync | Configuración de canal Android |
getExpoPushTokenAsync | Token de push de Expo |
scheduleNotificationAsync | Alertas locales programadas |
addNotificationReceivedListener | Recepción en primer plano |
addNotificationResponseReceivedListener | Usuario tocó notificación |
getLastNotificationResponseAsync | Manejo de toque de inicio en frío |
getExpoPushTokenAsync lanza una excepción. Solución: npx eas init y commit extra.eas.projectId.setNotificationChannelAsync antes del primer post.index.ts o parte superior de _layout.tsx.data.token en push es interceptable. Solución: Intercambiar IDs opacos del lado del servidor.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
expo-notifications local | Recordatorios programados, alertas sin conexión | Marketing impulsado por servidor a escala sin backend |
| Expo push + EAS | Apps de Expo nuevas | Ya tienes integración directa FCM/APNs |
| FCM/APNs directo | Control de carga útil personalizado | Quieres la configuración más rápida de Expo |
| Solo en la app (sin push) | El usuario siempre está en la app durante la tarea | Trabajadores de campo que colocan la app en fondo |
npx expo install expo-notifications expo-device expo-constantsAgrega el plugin de configuración en app.config.ts para iconos y sonidos personalizados. Reconstruye el binario nativo después de cambios de plugins.
Device.isDevice es falso en simuladores.const last = await Notifications.getLastNotificationResponseAsync();
if (last) navigateFromNotification(last);channelId en contenido de scheduleNotificationAsync para local; las cargas remotas establecen channelId en Android.Versiones de Stack: Esta página se escribió para React 19.2.3, React Native 0.86.0 y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 16 jul 2026