Expo Push (Classic)

Rich notifications

Send subtitle, badge, ttl, sound, channel and other Expo message fields with the native-notify send helpers.

Sends are live

Every helper sends the moment you call it. There's no test mode, and a send can't be recalled.

Send helpers

Needs native-notify v5.2 or later (npm install native-notify@^5.2.0). See Upgrading to v5.

HelperSends to
sendMassNotification(title, body, options)Every registered device of the app.
sendIndieNotification(subID, title, message, options)One individual-push subscriber.
sendIndieGroupNotification(subIDs, title, message, options)A list of subscriber ids you already hold.
sendNotificationToFollowers(masterSubID, title, message, options)Every follower of one follow-master subID.

All four take the same options. They pass Expo's message fields through to the messages Native Notify delivers through Expo, and the REST endpoints and Native Notify's send form accept the same fields.

Send a rich notification

import {
  sendMassNotification,
  sendIndieNotification,
  sendIndieGroupNotification,
  sendNotificationToFollowers,
} from 'native-notify';

// Every registered device (mass):
await sendMassNotification('Service update', 'We are back online.', {
  pushData: { url: '/status' },
  subtitle: 'All systems normal',
  channelId: 'alerts',          // Android channel — create it first (below)
  interruptionLevel: 'active',  // iOS
  sound: 'chime.wav',
});

// One subscriber (indie):
await sendIndieNotification(userId, 'Your order', 'It shipped!', {
  pushData: { url: '/orders/' + orderId },
  badge: 1,
  categoryId: 'ORDER_UPDATE',
});

// A custom audience, or the followers of a follow-master:
await sendIndieGroupNotification(subIds, 'Group news', 'Hello all', { channelId: 'news' });
await sendNotificationToFollowers(masterSubId, 'New post', 'Read it', { mutableContent: true });

Each helper resolves with the server's response body:

ResultWhen
Resolves with "Success!"The push went out.
Resolves with an explanation (status 201)A lapsed trial, a Premium plan on group and follower sends, an unregistered indie subID, or a follow-master with no followers. Check the body: resolving isn't delivery.
Throws on an error status400 for a missing title or body, an invalid rich field, or a message over Expo's 4 KiB limit (notification payload exceeds Expo's 4KiB limit).
Throws on timeoutA helper gives up after 10 seconds while the server keeps sending, so don't retry blindly.
Throws before sendingsendIndieGroupNotification when subIDs isn't a non-empty array.

Options

OptionTypeNotes
pushDataobjectThe data your app reads on tap (deep links, ids).
bigPictureURLstringImage URL. Android devices that Native Notify delivers to directly through FCM v1 show it. Expo-delivered messages don't carry it.
subtitlestring (max 200 chars)iOS subtitle under the title.
badgenumber (non-negative integer)iOS app icon badge count. 0 clears it.
ttlnumber (> 0)Seconds the push may be delivered for.
interruptionLevel'passive' | 'active' | 'timeSensitive' | 'critical'iOS interruption level. Use timeSensitive and critical sparingly.
categoryIdstringiOS notification category (your action buttons).
channelIdstringAndroid channel id. Create the channel first.
collapseIdstringAPNs collapse id. A newer push with the same id replaces the older one.
contentAvailablebooleaniOS background delivery (silent data update).
mutableContentbooleanLets an iOS notification service extension modify the payload, for example to attach an image.
soundstring | falseCustom sound file name, or false for a silent notification.
appId / appTokennumber | stringOptional overrides. Normally set once with NativeNotify.init(). The send helpers don't read <NativeNotifyProvider>.
  • Only the options you pass are sent, and the server validates them up front: an invalid value (an unknown interruptionLevel, a non-boolean mutableContent) fails with 400, not per recipient at Expo. sound: false, badge: 0 and contentAvailable: false are real values, not omissions.
  • Raw REST calls also accept expiration, a unix timestamp, as an alternative to ttl.
  • The fields apply however a device is reached. If an app saved its own FCM v1 service account or APNs key in Native Notify, its devices are delivered directly through FCM and APNs and the fields travel in those payloads: sound, badge, subtitle, ttl and the rest on iOS, high priority and the data fields on Android.
  • Mass and single-indie sends carry the hidden notification id that Sessions & push opens reports on. Enable it with analytics: { opens: true }, with your ids set in NativeNotify.init() or passed to registerNNPushToken.

Android notification channels

A channelId has to match a channel the app has created on the device. setAndroidNotificationChannel(channelId, options) wraps expo-notifications' setNotificationChannelAsync(), so create the channel before you send to it:

import * as Notifications from 'expo-notifications';
import { setAndroidNotificationChannel, sendMassNotification } from 'native-notify';

await setAndroidNotificationChannel('alerts', {
  name: 'Alerts',
  importance: Notifications.AndroidImportance.HIGH,
  sound: 'chime.wav',
  vibrationPattern: [0, 250, 250, 250],
});

// Then send to it:
await sendMassNotification('Heads up', 'The service is back', { channelId: 'alerts' });

Only the keys you set are used:

OptionTypeNotes
namestringName shown in Android settings. Defaults to the channel id.
descriptionstringDescription shown in Android settings.
importanceNotifications.AndroidImportanceMAX, HIGH, DEFAULT, LOW or MIN.
soundstring | nullCustom sound file name. Omit for the platform default.
vibrationPatternnumber[]Vibration pattern in ms, like [0, 250, 250, 250].
lightColorstringNotification LED color, like '#FF231F7C'.
enableLightsbooleanEnable the notification LED.
enableVibratebooleanEnable vibration for this channel.
showBadgebooleanShow a badge dot on the app icon for this channel's notifications.

It resolves true on Android. On iOS and the web, or when the call fails, it resolves false and never throws, so callers keep their default channel.