# Publishing notifications

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

`client.notifications` is the client's list of notifications: short messages about what just happened, such as "Error uploading attachment" or "Settings saved". The SDK publishes to it when an upload, a send or another operation goes wrong, and your code can publish to it too. A UI reads the list and shows each notification for a while, or until the user closes it.

>
> **Info:** **Example**
>
> The user adds a 300 MB video to a message, but the Stream app accepts files of up to 100 MB:
>
> 1. The composer's upload middleware refuses the file and calls `client.notifications.addError()`.
> 2. The notification goes into `client.notifications.store`, with the severity `error`, the type `validation:attachment:upload:blocked` and the reason `size_limit` in its metadata.
> 3. The SDK's `NotificationList` shows it, and starts its countdown with `client.notifications.startTimeout(id)`. After three seconds it's removed.
>

Displaying notifications, where each one is shown, and the ones the SDK's components publish, are on [Displaying notifications](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/displaying-notifications/).

## Publishing a notification

`client.notifications.add()` publishes a notification and returns its id. It takes the `message` to show, an `origin` that says who published it, and `options`:

```ts
const id = client.notifications.add({
  message: "Settings saved",
  origin: { emitter: "SettingsScreen" },
  options: { severity: "success", type: "app:settings:save:success" },
});
```

`client.notifications.addError()`, `client.notifications.addWarning()`, `client.notifications.addInfo()` and `client.notifications.addSuccess()` do the same with the severity set:

```ts
const saveSettings = async (save: () => Promise<void>) => {
  try {
    await save();
    client.notifications.addSuccess({
      message: "Settings saved",
      origin: { emitter: "SettingsScreen" },
    });
  } catch (error) {
    client.notifications.addError({
      message: "Could not save the settings",
      origin: { context: { section: "profile" }, emitter: "SettingsScreen" },
      options: {
        originalError: error instanceof Error ? error : undefined,
        type: "app:settings:save:failed",
      },
    });
  }
};
```

What a notification carries:

- `message`: the text. The SDK's own messages are plain English for developers; a UI should translate by `type`.
- `origin`: `{ emitter, context? }`. The emitter names the publisher, such as `"AttachmentManager"`, so a UI can pick out some publishers. `context` holds what a UI needs to know, such as the attachment.
- `options.severity`: `'error'`, `'warning'`, `'info'`, `'success'`, or a string of your own.
- `options.type`: what the notification is about. See [Notification types](#notification-types).
- `options.actions`: buttons, each `{ label, handler }`.
- `options.duration`: how long to show it, in milliseconds.
- `options.metadata`: extra data, such as the reason for a failure.
- `options.tags`: labels a UI can use to decide where to show it.
- `options.originalError`: the error that caused it.

`client.notifications.remove(id)` removes one notification, and `client.notifications.clear()` removes them all.

`client.notifications.store` holds the list as `{ notifications }`. Subscribe to it to follow the list outside a component, as with any [store](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/reactive-state/). In a component, `useNotifications()` returns the notifications in it.

The SDK shows notifications per screen area, such as the channel list, a channel or a thread, and reads where each one belongs from its tags. In a component, `useNotificationApi().addNotification()` publishes to the same list and tags the notification for the area the component sits in. [Displaying notifications](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/displaying-notifications/) explains the targets.

## Notification types

A notification's `type` says what it's about, so a UI can translate it or group it. The types `stream-chat` publishes follow the pattern `domain:entity:operation:result`:

- `domain`: where it happened, such as `api` for a request or `validation` for a check before one.
- `entity`: what it's about, such as `attachment`, `poll` or `message`.
- `operation`: what was attempted, such as `upload`, `create` or `castVote`.
- `result`: how it went, such as `failed`, `blocked`, `limit` or `success`.

So `api:attachment:upload:failed` is an upload request that failed. Every type `stream-chat` publishes is in `CORE_NOTIFICATION_TYPE`, and the values don't change between minor releases. For your own notifications, any string works, but the same pattern keeps them easy to tell apart. Match on `type`, never on `message`: the message is a fallback whose wording can change. The SDK translates the `stream-chat` notifications it shows by their `type`, as [Localization](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/localization/) explains.

## Severity, actions and durations

The severity decides how long a notification stays: three seconds for each of `error`, `warning`, `info` and `success` by default. Change the durations in the client's [configuration](https://getstream.io/chat/docs/sdk/react-native/v10/state/state-and-entities/configuration/). Naming one severity keeps the others:

```ts
client.config.set({
  client: { notifications: { durations: { error: 8000 } } },
});
```

`options.duration` overrides the severity's duration for one notification. A notification stays until it's removed when its duration is `0`, or when neither it nor its severity has a duration. A severity of your own has no duration unless you add it to `durations`.

```ts
import { Linking } from "react-native";
import { StreamChat } from "stream-chat";

const client = StreamChat.getInstance("YOUR_API_KEY");

client.notifications.addWarning({
  message: "You're offline. Messages will be sent when you reconnect.",
  origin: { emitter: "ConnectionBanner" },
  options: {
    actions: [
      { handler: () => void Linking.openSettings(), label: "Open settings" },
    ],
    duration: 0,
  },
});
```

The countdown doesn't start on its own. It starts with `client.notifications.startTimeout(id)`, which the UI calls when it shows the notification, so a notification nobody has seen yet isn't removed. `client.notifications.ensureTimeout(id)` starts it only if it isn't running yet, and `client.notifications.clearTimeout(id)` stops it, for example while the user interacts with the notification.

The manager only keeps the actions. Showing them as buttons and calling `handler` is up to the UI.

## Who publishes notifications

These are the notifications `stream-chat` publishes:

| Type                                       | Severity | Published by        | When                                                                                                          |
| ------------------------------------------ | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
| `api:attachment:upload:failed`             | error    | `AttachmentManager` | An upload request failed. Not for an upload the user canceled.                                                |
| `validation:attachment:upload:blocked`     | error    | `AttachmentManager` | The Stream app's upload settings, set in the Dashboard, refused a file. `metadata.reason` says which setting. |
| `validation:attachment:file:missing`       | error    | `AttachmentManager` | An attachment to upload had no file.                                                                          |
| `validation:attachment:id:missing`         | error    | `AttachmentManager` | An attachment to upload had no local id.                                                                      |
| `validation:attachment:upload:in-progress` | warning  | `MessageComposer`   | A message was sent while an upload was running, with pending uploads off.                                     |
| `validation:command:disabled`              | warning  | `MessageComposer`   | A command was used while editing a message, or a moderation command while quoting one.                        |
| `validation:command:not-ready`             | warning  | `MessageComposer`   | A command was sent without what it needs, such as `/ban` without a user.                                      |
| `api:message:send:failed`                  | error    | `MessageComposer`   | `send()` failed. The message stays in the list, marked `failed`.                                              |
| `api:message:update:failed`                | error    | `MessageComposer`   | `update()` failed. The edit stays on the message, marked `failed`.                                            |
| `api:poll:create:failed`                   | error    | `MessageComposer`   | `createPoll()` failed.                                                                                        |
| `api:location:create:failed`               | error    | `MessageComposer`   | `sendLocation()` failed.                                                                                      |
| `api:message:jump:failed`                  | error    | `MessagePaginator`  | Jumping to a message failed.                                                                                  |
| `api:message:jumpToLatest:failed`          | error    | `MessagePaginator`  | Jumping to the latest message failed.                                                                         |
| `validation:poll:castVote:limit`           | info     | `Poll`              | A vote was cast in a poll where the user has used all their votes.                                            |

The message list's emitters carry the method name, such as `MessagePaginator.jumpToMessage`.

With offline support, a send or an edit that fails because the device is offline publishes `api:message:send:failed` or `api:message:update:failed` too, although it waits as a pending task and is sent after the next reconnect.

Your own code publishes with the same calls. The SDK's components publish more, for example when a message action succeeds; those are listed on [Displaying notifications](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/displaying-notifications/).

## Pitfalls

- **Matching on the message.** The SDK's messages are English fallbacks and can be reworded. Use `type`.
- **A notification that never goes away.** Its countdown waits for `client.notifications.startTimeout()`. A UI of your own has to call it, or `client.notifications.ensureTimeout()`, when it shows the notification.
- **The configuration key.** The durations live under `client.notifications` in `client.config.set()`, not under a top-level `notifications` key.

## Used in

- [Notifications](https://getstream.io/chat/docs/sdk/react-native/v10/features/app-experience/notifications/)
- [Sending with pending uploads](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/sending-with-pending-uploads/)
- [Connection status](https://getstream.io/chat/docs/sdk/react-native/v10/features/app-experience/connection-status/)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/state/publishing-notifications/](https://getstream.io/chat/docs/sdk/react-native/v10/state/publishing-notifications/).