Notifications

Read notification data and build custom inbox surfaces.

useNotifications()

useNotifications() is the main notification list hook. It is the same stateful surface that powers <NotificationPanel />, including pagination, optimistic read and archive actions, and live inserts from the notification stream.

export default function Inbox() {  const {    loading,    notifications,    hasMore,    markAsRead,    archiveNotification,    loadMore,  } = useNotifications({    scope: 'all',    is_archived: false,  });  if (loading) {    return <div>Loading notifications...</div>;  }  return (    <div className="space-y-4">      <ul className="space-y-2">        {notifications.map((notification) => (          <li key={notification.id} className="rounded-xl border p-4">            <div className="font-medium">{notification.title}</div>            <p className="text-sm text-muted-foreground">{notification.body}</p>            <div className="mt-3 flex gap-2">              {!notification.is_read ? (                <button onClick={() => markAsRead(notification.id)}>Mark as read</button>              ) : null}              <button onClick={() => archiveNotification(notification.id)}>                {notification.is_archived ? 'Unarchive' : 'Archive'}              </button>            </div>          </li>        ))}      </ul>      {hasMore ? <button onClick={loadMore}>Load more</button> : null}    </div>  );}
params?: UseNotificationsOptions | undefined | undefined;
Optional list filters and stream options. `UseNotificationsOptions` extends `NotificationListParams` and also accepts `onNotification`.
limit?: number | undefined | undefined;
Page size for each fetch.
cursor?: string | undefined | undefined;
Explicit cursor for the first page when you need to start from a known notification boundary.
scope?: 'all' | 'current' | 'user' | 'organization' | 'workspace' | undefined | undefined;
High-level notification scope. This also influences which stream channels are subscribed when `channels` is not passed explicitly.
channels?: ('user' | 'organization' | 'workspace' | 'current' | 'all')[] | undefined | undefined;
Optional channel filter for the list query. When present, these channels are also used for the stream subscription.
organization_ids?: string[] | undefined | undefined;
Restricts the list to specific organizations.
workspace_ids?: string[] | undefined | undefined;
Restricts the list to specific workspaces.
is_read?: boolean | undefined | undefined;
Filters by read state.
is_archived?: boolean | undefined | undefined;
Filters by archived state. This also changes how `archiveNotification()` updates the local list.
is_starred?: boolean | undefined | undefined;
Filters by starred state.
severity?: 'info' | 'success' | 'warning' | 'error' | undefined | undefined;
Filters by notification severity.
onNotification?: ((notification: NotificationMessage) => void) | undefined | undefined;
Runs when a newly streamed notification is inserted into the cached list.
loading?: boolean | undefined;
Stays `true` until the client and the first list page are ready. While loading is true, the action methods are not usable yet.
notifications?: Notification[] | undefined;
The flattened notification list for the current query.
id?: string | undefined;
Notification identifier.
deployment_id?: string | undefined;
Deployment that owns the notification.
user_id?: string | undefined;
User that the notification belongs to.
organization_id?: string | undefined | undefined;
Organization scope for the notification when it was emitted in an organization context.
workspace_id?: string | undefined | undefined;
Workspace scope for the notification when it was emitted in a workspace context.
title?: string | undefined;
Short notification title.
body?: string | undefined;
Notification body text.
ctas?: { label: string; payload: any }[] | undefined | undefined;
Optional call-to-action metadata attached to the notification.
label?: string | undefined;
CTA label shown to the user.
payload?: any | undefined;
Opaque CTA payload emitted with the notification.
severity?: 'info' | 'success' | 'warning' | 'error' | undefined;
Severity level for styling and filtering.
is_read?: boolean | undefined;
Read state.
read_at?: string | undefined | undefined;
Timestamp for when the notification was marked as read.
is_archived?: boolean | undefined;
Archived state.
archived_at?: string | undefined | undefined;
Timestamp for when the notification was archived.
is_starred?: boolean | undefined;
Starred state.
metadata?: Record<string, any> | undefined | undefined;
Optional notification metadata.
created_at?: string | undefined;
Creation timestamp.
updated_at?: string | undefined;
Last update timestamp.
expires_at?: string | undefined | undefined;
Expiry timestamp when the notification is time-bound.
hasMore?: boolean | undefined;
Whether the hook still has another page of notifications to fetch.
error?: Error | null | undefined;
The SWR loading error for the list query, if one occurred.
markAsRead?: (notificationId: string) => Promise<void> | undefined;
Marks one notification as read with an optimistic cache update, then revalidates the list.
markAllAsRead?: () => Promise<void> | undefined;
Marks every notification in the current query as read with the current filters applied.
archiveAllRead?: () => Promise<void> | undefined;
Archives the read notifications in the current filtered view and then reloads the list.
archiveNotification?: (notificationId: string) => Promise<void> | undefined;
Archives one notification, or removes it from the current archived view when `is_archived` is already part of the filter.
starNotification?: (notificationId: string) => Promise<void> | undefined;
Toggles the starred state on a single notification with an optimistic update.
markAsUnread?: (notificationId: string) => Promise<void> | undefined;
Marks one notification as unread with an optimistic cache update, then revalidates the list.
refetch?: () => Promise<void> | undefined;
Revalidates the current paginated list.
loadMore?: () => Promise<void> | undefined;
Loads the next page unless a page load is already in progress or there is no next page.

useNotificationStream()

useNotificationStream() manages the live websocket connection for notification updates. It is the low-level stream hook that useNotifications() uses to prepend new notifications into the cached list, but you can also use it directly when you need connection state or custom live handling.

import { useNotificationStream } from '@wacht/nextjs';export default function NotificationConnectionStatus() {  const { isConnected, connectionError, reconnect } = useNotificationStream({    channels: ['user', 'organization'],  });  return (    <div>      <div>{isConnected ? 'Connected' : 'Disconnected'}</div>      {connectionError ? <p>{connectionError}</p> : null}      <button onClick={reconnect}>Reconnect</button>    </div>  );}
enabled?: boolean | undefined;
Turns the stream on or off. Defaults to `true`.
channels?: string[] | undefined;
Limits the live feed to the selected channels.
organizationIds?: number[] | undefined;
Scopes the stream to specific organizations.
workspaceIds?: number[] | undefined;
Scopes the stream to specific workspaces.
onNotification?: (notification: NotificationMessage) => void | undefined;
Called whenever a live notification event arrives.
onError?: (error: string) => void | undefined;
Called when the websocket stream reports an error.
reconnectDelay?: number | undefined;
Base reconnect delay in milliseconds. Defaults to `1000` and then doubles on each failed reconnect attempt.
maxReconnectAttempts?: number | undefined;
Maximum number of reconnect attempts before the hook stops retrying. Defaults to `5`.
isConnected?: boolean | undefined;
Whether the websocket is currently open.
connectionError?: string | null | undefined;
The latest connection error, if the stream failed or exhausted its reconnect attempts.
disconnect?: () => void | undefined;
Closes the current socket and stops the keepalive timer.
reconnect?: () => void | undefined;
Clears the retry counter and starts a fresh connection attempt.
NotificationMessage?: { id: number; user_id: number; deployment_id: number; title: string; body: string; severity: string; ctas?: { label: string; payload: any }[]; created_at: string } | undefined;
Shape of the live notification payload passed to `onNotification`.
id?: number | undefined;
Notification identifier in the live stream payload.
user_id?: number | undefined;
Numeric user identifier for the notification recipient.
deployment_id?: number | undefined;
Numeric deployment identifier for the emitting deployment.
title?: string | undefined;
Notification title.
body?: string | undefined;
Notification body text.
severity?: string | undefined;
Severity emitted by the stream payload.
ctas?: { label: string; payload: any }[] | undefined | undefined;
Optional CTA payloads included with the live message.
label?: string | undefined;
CTA label.
payload?: any | undefined;
Opaque CTA payload.
created_at?: string | undefined;
Creation timestamp for the streamed notification.

useNotificationUnreadCount()

useNotificationUnreadCount() is the lightweight unread-count hook behind <NotificationBell /> and other summary UI. Use it when you only need the unread total for a scope and do not need the full notification list.

import { useNotificationUnreadCount } from '@wacht/nextjs';export default function NotificationBadge() {  const { loading, count } = useNotificationUnreadCount({ scope: 'current' });  if (loading || count === 0) {    return null;  }  return <span>{count}</span>;}
params?: NotificationListParams | undefined | undefined;
Optional unread-count filters.
limit?: number | undefined | undefined;
Accepted for consistency with the shared notification params shape, though the unread-count response itself is still a single number.
cursor?: string | undefined | undefined;
Accepted by the shared params shape, though the unread-count response is not paginated.
scope?: 'all' | 'current' | 'user' | 'organization' | 'workspace' | undefined | undefined;
High-level notification scope for the unread count.
channels?: ('user' | 'organization' | 'workspace' | 'current' | 'all')[] | undefined | undefined;
Optional channel filter.
organization_ids?: string[] | undefined | undefined;
Restricts the unread count to specific organizations.
workspace_ids?: string[] | undefined | undefined;
Restricts the unread count to specific workspaces.
is_read?: boolean | undefined | undefined;
Filters by read state.
is_archived?: boolean | undefined | undefined;
Filters by archived state.
is_starred?: boolean | undefined | undefined;
Filters by starred state.
severity?: 'info' | 'success' | 'warning' | 'error' | undefined | undefined;
Filters by severity.
loading?: boolean | undefined;
Stays `true` until the client and the unread-count query are ready.
count?: number | undefined;
The unread count for the requested scope.
error?: Error | null | undefined;
The loading error, if the unread count request failed.
refetch?: () => Promise<void> | undefined;
Refreshes the unread count.