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.

Info:

Example

The user adds a 30 MB video to a message, but the app only accepts 20 MB files:

  1. The composer refuses the file and publishes an error notification about it, with the reason: the file is too large.
  2. The UI shows the notification and starts its countdown.
  3. 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 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.

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 under client.notifications.durations, as in { client: { notifications: { durations: { error: 8000 } } } }. A top-level notifications key isn't a setting, so the durations stay at their defaults.

Used in