Expo Push (Classic)

Prebuilt mass inbox component

Add a bell with an unread dot and a full-screen mass notification inbox to your app header.

Premium has no inbox

On Premium, every inbox endpoint answers 201 with an explanation ("Your current Native Notify membership cannot access notification inbox features…"), so the SDK shows an empty inbox and an unread count of 0 until you upgrade. Notifications sent meanwhile are kept. See what each plan includes.

Quick start

Add <NotificationInboxBell> to your header. mode="mass" is the default:

import { NotificationInboxBell } from 'native-notify';

// Expo Router / React Navigation header:
<Stack.Screen
  name="index"
  options={{
    headerRight: () => (
      <NotificationInboxBell
        appId={yourAppId}
        appToken="yourAppToken"
        mode="mass" // default — can be omitted
      />
    ),
  }}
/>

Tapping the bell opens the built-in inbox screen with your mass notifications, the data getNotificationInbox returns, and shows an unread dot for new ones. appId and appToken are optional when you set them once with NativeNotify.init() or <NativeNotifyProvider>.

Mass mode behavior

Unread dot

The dot needs an Expo push token, because read state is tracked per device by that token. It never shows on web, in Expo Go on Android (SDK 53+) or on a simulator or emulator without push support, so test on a physical device.

  • Opening the inbox marks every notification as read for this device, not only the fetched page, so the dot clears. perNotificationRead turns this off.
  • The app icon badge follows the unread count through Notifications.setBadgeCountAsync(). Pass syncBadge={false} to opt out.

Per-notification read state

Requires native-notify v5.2+. Pass perNotificationRead to keep a read state per row instead of marking the whole inbox read on open:

<NotificationInboxBell
  appId={yourAppId}
  appToken="yourAppToken"
  perNotificationRead
/>

Rows carry their real read flag for this device. Unread rows show a dot, and tapping a row marks it read and refreshes the unread count, which comes from the server instead of being zeroed on open. With no Expo push token (see the note above), every row reports unread and can't be marked. It's off by default, so existing apps make the same requests as before.

Props

NotificationInboxBell accepts:

PropTypeDefaultDescription
appIdnumber | string—Your app id.
appTokenstring—Your app token.
mode'mass' | 'indie''mass'Which inbox to load.
subIdnumber | string—Required when mode="indie": the user's unique id.
takenumber20Notifications fetched per page.
syncBadgebooleantrueSync the unread count to the app icon badge.
perNotificationReadbooleanfalsePer-row read state. See Per-notification read state.
colorsobjectlight/dark themePartial theme override. See Colors.
titlestring'Notifications'Inbox header title.
emptyTextstring"You're all caught up"Empty-state text.
allowDeleteboolean—Per-row delete button. Indie mode only: a mass delete would remove the notification for every user, so it's a dashboard action.
onNotificationPress(notification) => void—Called when a row is tapped.
onOpen() => void—Called instead of opening the built-in inbox screen.
showCountbooleanfalseShow a numeric unread badge instead of a plain dot.
maxCountnumber99Badge cap. Shows "99+" past it.
renderIcon({ unreadCount, color }) => nodebundled bell iconCustom bell icon.
iconSizenumber24Bell icon size.
iconStyleStyleProp<ImageStyle>—Style override for the bell icon.
containerStyleStyleProp<ViewStyle>—Style override for the bell container.

Colors

Colors follow the device's light or dark mode. Pass any subset of these keys in colors: icon, dot, badgeText, background, headerBackground, card, border, title, text, mutedText, emptyTitle, emptyText, accent, delete.

<NotificationInboxBell
  appId={yourAppId}
  appToken="yourAppToken"
  colors={{ dot: '#EF4444', accent: '#2563EB' }}
/>

Custom screen and headless hook

For your own trigger, render <NotificationInboxScreen> and control the modal with visible and onClose. It takes the props above except the bell-only ones (onOpen, showCount, maxCount, renderIcon, iconSize, iconStyle, containerStyle), plus:

PropTypeDefaultDescription
visibleboolean—Whether the inbox modal is shown.
onClose() => void—Called when the modal is dismissed.

For a fully custom UI, use the headless hook:

import { useNotificationInbox } from 'native-notify';

const {
  notifications,   // fetched inbox rows
  unreadCount,     // unread count for your own badge
  loading,         // initial load in progress
  refreshing,      // pull-to-refresh in progress
  loadingMore,     // next page in progress
  hasMore,         // more pages available
  error,           // error message, if any
  openInbox,       // loads page 1 — and marks the whole inbox read, unless perNotificationRead
  refresh,
  refreshUnread,
  loadMore,
  deleteNotification,
  perNotificationRead, // true when you built the hook with the option
  markNotificationRead, // mark ONE row read when it is opened (per-notification mode)
} = useNotificationInbox({
  appId: yourAppId,
  appToken: 'yourAppToken',
  mode: 'mass',
  take: 20,
  syncBadge: true, // sync the unread count to the app icon badge
});

The hook fetches only the unread count on mount. Call openInbox() or refresh() to load the rows. markNotificationRead takes the row object, not an id. For exact pagination, use getNotificationInboxPage().

Inbox row data

{
  notification_id, // unique notification ID
  date,            // notification date
  title,           // notification title
  message,         // notification message
  pushData,        // the pushData you sent, as a JSON string — JSON.parse(row.pushData)
  read,            // this device's read state — authoritative with perNotificationRead
}