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.
perNotificationReadturns this off. - The app icon badge follows the unread count through
Notifications.setBadgeCountAsync(). PasssyncBadge={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:
| Prop | Type | Default | Description |
|---|---|---|---|
appId | number | string | — | Your app id. |
appToken | string | — | Your app token. |
mode | 'mass' | 'indie' | 'mass' | Which inbox to load. |
subId | number | string | — | Required when mode="indie": the user's unique id. |
take | number | 20 | Notifications fetched per page. |
syncBadge | boolean | true | Sync the unread count to the app icon badge. |
perNotificationRead | boolean | false | Per-row read state. See Per-notification read state. |
colors | object | light/dark theme | Partial theme override. See Colors. |
title | string | 'Notifications' | Inbox header title. |
emptyText | string | "You're all caught up" | Empty-state text. |
allowDelete | boolean | — | 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. |
showCount | boolean | false | Show a numeric unread badge instead of a plain dot. |
maxCount | number | 99 | Badge cap. Shows "99+" past it. |
renderIcon | ({ unreadCount, color }) => node | bundled bell icon | Custom bell icon. |
iconSize | number | 24 | Bell icon size. |
iconStyle | StyleProp<ImageStyle> | — | Style override for the bell icon. |
containerStyle | StyleProp<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:
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | — | 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
}