Expo Server SDK for Node.js
The `expo-server-sdk` is a server-side library for Node.js environments, designed to facilitate the sending of push notifications to mobile applications built with Expo. It provides utilities for constructing push notification messages, sending them efficiently to Expo's push notification service, and handling the resulting receipts and errors. The current stable version is 6.1.0. This SDK maintains an active release cadence, with recent major versions (v4, v5, v6) introducing significant breaking changes around module systems and Node.js version support. Key differentiators include its tight integration with the Expo ecosystem, simplifying the complex process of managing push tokens and batching messages, and providing a streamlined API for interacting with the Expo push service. It abstracts away much of the underlying HTTP request handling and error parsing.
Common errors
-
TypeError: require is not a function
cause Attempting to import `expo-server-sdk` using CommonJS `require()` syntax in a project that is configured as an ES Module (or with `expo-server-sdk` v6+).fixChange `const Expo = require('expo-server-sdk');` to `import { Expo } from 'expo-server-sdk';` and ensure your `package.json` has `"type": "module"` or use `.mjs` file extensions for files importing ESM. -
RangeError: Push token ... is not a valid Expo push token.
cause The string provided as a push token does not conform to the expected format for Expo push tokens (e.g., 'ExponentPushToken[xxxxxxxxxxxxxxxxx]'). This often happens with malformed tokens or tokens from other push notification services.fixAlways validate tokens using `Expo.isExpoPushToken(token)` before attempting to send notifications. Ensure you are receiving valid tokens from Expo's client-side SDK. -
Error: Minimum Node.js version 20.x is required.
cause Running the `expo-server-sdk` (v4.0.0 or higher) with an incompatible Node.js version, such as Node.js 18 or older.fixUpgrade your Node.js installation to version 20 or newer. Use a Node.js version manager like `nvm` to easily switch versions (`nvm install 20 && nvm use 20`). -
{ 'status': 'error', 'message': 'The push token is not subscribed to this channel.', 'details': { 'error': 'MessageRateLimitExceeded' } } (example receipt error)cause Push notification service returns an error receipt, indicating issues like an invalid token, rate limiting, or a token not being subscribed to the specified channel.fixImplement robust error handling for push notification receipts. Log errors and potentially remove invalid/expired tokens from your database. For rate limits, consider implementing exponential backoff or reviewing your sending strategy.
Warnings
- breaking Starting with v6.0.0, 'expo-server-sdk' is built as an ECMAScript module (ESM) only. This means that CommonJS `require()` statements will no longer work for importing the main package. Projects must migrate to ESM `import` syntax.
- breaking Version 5.0.0 removed the `useFcmV1` option. This option, which previously allowed switching between Firebase Cloud Messaging (FCM) API versions, is no longer supported or necessary, as the library now exclusively uses the recommended API version.
- breaking Version 5.0.0 replaced `node-fetch` with `undici` as its internal HTTP client. While this is primarily an internal change, it might affect applications that relied on `node-fetch` specific behaviors, configurations, or extensions. It also potentially changes how network errors are handled internally.
- breaking Support for Node.js v18.x was dropped in v4.0.0. The package now requires Node.js v20.x or higher to function correctly.
- deprecated The `useFcmV1` option was deprecated in v3.11.0 and defaulted to `true`. It was subsequently removed entirely in v5.0.0. While deprecated, it still functioned in v3.11.0 - v4.x, but its removal in v5.0.0 is a breaking change.
Install
-
npm install expo-server-sdk -
yarn add expo-server-sdk -
pnpm add expo-server-sdk
Imports
- Expo
const Expo = require('expo-server-sdk');import { Expo } from 'expo-server-sdk'; - ExpoPushMessage
import { type ExpoPushMessage } from 'expo-server-sdk';import { ExpoPushMessage } from 'expo-server-sdk'; - isExpoPushToken
import isExpoPushToken from 'expo-server-sdk';
import { isExpoPushToken } from 'expo-server-sdk';
Quickstart
import { Expo, ExpoPushMessage, ExpoPushTicket, ExpoPushReceiptId } from 'expo-server-sdk';
const expo = new Expo();
const sendPushNotification = async (token: string, message: string, data?: object) => {
if (!Expo.isExpoPushToken(token)) {
console.error(`Push token ${token} is not a valid Expo push token.`);
return;
}
const messages: ExpoPushMessage[] = [{
to: token,
sound: 'default',
body: message,
data: data || { withSome: 'data' },
channelId: 'default',
}];
const chunks = expo.chunkPushNotifications(messages);
const tickets: ExpoPushTicket[] = [];
for (const chunk of chunks) {
try {
const ticketChunk = await expo.sendPushNotificationsAsync(chunk);
tickets.push(...ticketChunk);
console.log('Push tickets received:', ticketChunk);
} catch (error) {
console.error('Error sending push chunk:', error);
}
}
const receiptIds: ExpoPushReceiptId[] = [];
for (const ticket of tickets) {
if (ticket.id) {
receiptIds.push(ticket.id);
}
}
if (receiptIds.length > 0) {
const receiptIdChunks = expo.chunkPushReceiptIds(receiptIds);
for (const chunk of receiptIdChunks) {
try {
const receipts = await expo.getPushNotificationReceiptsAsync(chunk);
console.log('Push receipts received:', receipts);
for (const receiptId in receipts) {
const { status, message, details } = receipts[receiptId];
if (status === 'error') {
console.error(`There was an error sending a notification: ${message}`);
if (details && details.error) {
console.error(`The error code is ${details.error}`);
}
}
}
} catch (error) {
console.error('Error getting push receipts:', error);
}
}
}
};
// Example usage (replace with actual token and message)
const exampleToken = process.env.EXPO_PUSH_TOKEN ?? 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]';
if (exampleToken) {
sendPushNotification(exampleToken, 'Hello from Expo!', { 'foo': 'bar' });
}