Publishing notifications
client.notifications is the client's notification manager: the service through which notifications are published and managed. A notification is an object that describes what just happened, such as a failed upload. It carries the text to show, such as "Error uploading attachment", its severity, who published it, and a type that identifies what it's about. A UI uses the type to show a translated text instead. The SDK publishes one when an upload, a send or another operation goes wrong, and your code can publish its own. The manager keeps the current notifications in its store, and a UI reads them from there and shows each one for a while, or until the user closes it. Use this page to publish notifications of your own, to translate or filter the SDK's by type, and to control how long each one stays.
Example
The user adds a 30 MB video to a message, but the app only accepts 20 MB files:
- The composer refuses the file and publishes an error notification about it, with the reason: the file is too large.
- The UI shows the notification and starts its countdown.
- After three seconds, the notification is removed.
How notifications are shown is on Notifications UI, and the ones the React components publish are listed on 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:
const id = client.notifications.add({
message: "Settings saved",
origin: { emitter: "SettingsPanel" },
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:
const saveSettings = async (save: () => Promise<void>) => {
try {
await save();
client.notifications.addSuccess({
message: "Settings saved",
origin: { emitter: "SettingsPanel" },
});
} catch (error) {
client.notifications.addError({
message: "Could not save the settings",
origin: { context: { section: "profile" }, emitter: "SettingsPanel" },
options: {
originalError: error instanceof Error ? error : undefined,
type: "app:settings:save:failed",
},
});
}
};What a notification carries:
| Field | What it holds |
|---|---|
message |
The text. The SDK's own messages are plain English for developers; a UI should translate by type. |
origin.emitter |
The publisher, such as "AttachmentManager", so a UI can pick out some publishers. |
origin.context |
What a UI needs to know, such as the attachment. Optional. |
options.severity |
'error', 'warning', 'info', 'success', or a string of your own. |
options.type |
What the notification is about. See Notification types. |
options.actions |
Data necessary for actions buttons displayed with the notification UI. 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. |
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 asapifor a request orvalidationfor a check before one.entity: what it's about, such asattachment,pollormessage.operation: what was attempted, such asupload,createorcastVote.result: how it went, such asfailed,blocked,limitorsuccess.
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.
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. Naming one severity keeps the others:
client.config.set({
client: { notifications: { durations: { error: 8000 } } },
});A notification disappears on its own after its duration: options.duration if it has one, or else the configured duration for its severity. It stays until it's removed when:
- its duration is
0; - it has no duration and no severity;
- its severity is one of your own, such as
'critical', with no duration configured for it.
client.notifications.addWarning({
message: "You're offline. Messages will be sent when you reconnect.",
origin: { emitter: "ConnectionBanner" },
options: {
actions: [{ handler: () => window.location.reload(), label: "Reload" }],
duration: 0,
},
});A notification can also offer actions: things the user can do about it, such as Reload in the example above. Each action in options.actions is { label, handler }. The manager only stores them with the notification: your UI shows each one as a button with its label, and calls its handler when the user presses it.
import type { Notification } from "stream-chat";
// Shows a notification's actions as buttons in an element of your own.
const showActions = (notification: Notification, container: HTMLElement) => {
const buttons = (notification.actions ?? []).map(({ handler, label }) => {
const button = document.createElement("button");
button.textContent = label;
button.addEventListener("click", () => {
handler();
// The user acted on the notification, so it can go.
client.notifications.remove(notification.id);
});
return button;
});
container.replaceChildren(...buttons);
};Following and managing notifications
The countdown doesn't start on its own: the UI starts it when it shows the notification, so a notification nobody has seen yet isn't removed.
| To… | Call | What happens |
|---|---|---|
| follow the list | subscribe to client.notifications.store |
It holds the list as { notifications }, like any store. |
| start the countdown when the notification is shown | client.notifications.startTimeout(id) |
The notification is removed once its duration has passed. |
| start the countdown only if it isn't running yet | client.notifications.ensureTimeout(id) |
Same as startTimeout(), but a running countdown isn't restarted. |
| pause the countdown, for example while the pointer rests on the notification | client.notifications.clearTimeout(id) |
The countdown stops. Start it again to resume. |
| remove one notification | client.notifications.remove(id) |
It leaves the list. |
| remove them all | client.notifications.clear() |
The list is empty. |
Who publishes notifications
Both stream-chat and stream-chat-react publish notifications of their own, and your code publishes with the same calls. Notifications lists every one they publish, with its type, severity, origin.emitter and when it's published.
Pitfalls
- Matching on the message. The SDK's messages are English fallbacks and can be reworded. Use
type. - A notification that never goes away. The manager removes a notification only when its countdown ends, and the countdown starts only when
client.notifications.startTimeout(id)is called. The SDK's notification components call it when they show a notification. A UI of your own has to call it too, or the notification stays in the list. - Durations set in the wrong place. In
client.config.set(), the durations go underclient.notifications.durations, as in{ client: { notifications: { durations: { error: 8000 } } } }. A top-levelnotificationskey isn't a setting, so the durations stay at their defaults.