Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

Configuration

The client builds most objects for you: channels, threads, composers, message lists, and its own managers for the connection, notifications and reminders. Each of them has settings, for example whether a composer saves drafts, how many messages a list loads per page, or how long the connection waits before it retries. This page goes through them in order: where settings come from, how your app sets them, how they combine, and how to read the result.

Client and server settings

An object's settings come from two places:

  • Client settings are what your app sets in code. They decide how the SDK's objects behave: for example, how many messages a list loads per page, whether composers save drafts, or how a message is sent.
  • Server settings live on the server, and your app only reads them: what the channel type allows, what the current user may do, slow mode and the user's privacy settings. See Server settings.

Some features need both: your app turns a feature on, and the server can still turn it off. Who decides what lists them. The next section is about client settings.

Client settings

The settings tree

The SDK's objects come in kinds: channels, threads, composers, message lists, and so on. Client settings are set per kind, and a setting applies to every object of its kind. They form a tree, with one key for each kind:

Key Configures
channel every channel: typing, read and delivery events, replies, reminders, request handlers, its message list and its pinned-messages list
thread every thread: its request handlers and its reply list
messageComposer every composer, in channels, threads and edits: text, attachments, drafts, link previews, polls, location, commands
messagePaginator every message list, a channel's and a thread's alike. channel.messagePaginator and thread.messagePaginator override it field by field
messageOperations how every channel and thread sends, edits and deletes messages and reactions. Its settings say how many failed sends it keeps for retry, and for how long. channel.messageOperations and thread.messageOperations override it
client the client and its managers: the connection, connection recovery, delivery receipts, notifications, reminders and the thread list
liveLocationManager live location sharing
searchController search

Every setting is listed in Configuration tree.

The keys are fixed: you can't add one, not even with module augmentation. To make a class of your own configurable, see Advanced: making your own class configurable.

When you create the client

Pass the tree to the StreamChat constructor. Say every composer should save drafts, and every channel's message list should load 50 messages per page:

import { StreamChat } from "stream-chat";

const chatClient = new StreamChat("YOUR_API_KEY", {
  channel: { messagePaginator: { pageSize: 50 } },
  messageComposer: { drafts: { enabled: true } },
});

StreamChat.getInstance(apiKey, config) takes the same tree the first time it's called, and ignores it after that.

A few settings are read only while the client is being built, so they have to go here; Settings read once lists them.

After the client is created

The client keeps the tree in client.config, and you can change it at any time. A change also reaches the objects that exist already, except for the few settings read once. It can also undo what you changed on one object with updateConfig(): see How the settings combine.

For example, to turn drafts off again:

client.config.set({
  messageComposer: { drafts: { enabled: false } },
});

client.config has these methods:

  • client.config.set(tree) merges into what's already set, so a later call changes only the paths it names.
  • client.config.setConfig(key, subtree) does the same for one key, such as "messageComposer".
  • client.config.getConfig(key) returns what you set for one key, and client.config.getTree() the whole tree.
  • client.config.reset(key?) clears client.config, for one key or for every key when you pass none. That includes the tree you passed to new StreamChat(), and the setup function, whose teardown runs. Every existing object then rebuilds its settings right away: it goes back to the SDK's defaults and drops its updateConfig() changes. It keeps the options your code passed when it created the object, such as a ChannelPaginator's paginatorOptions, and the server's limits still apply. Only the settings read once have no effect on existing objects: those objects already used them when they were created.

Settings for one object

The ways above apply to every object of a kind. Two ways change a single object:

  • Its constructor, for an object your code creates. A ChannelPaginator, for example, takes its settings when you create it, as Channel list paginators shows.
  • updateConfig(patch), which these objects have:
Call Changes
channel.messageComposer.updateConfig(patch) the channel's composer
channel.messagePaginator.updateConfig(patch) the channel's message list
channel.pinnedMessagesPaginator.updateConfig(patch) the channel's pinned-messages list
channel.messageOperations.updateConfig(patch) how the channel sends messages
Thread.updateConfig(patch) the thread
thread.messageComposer.updateConfig(patch) the thread's composer
thread.messagePaginator.updateConfig(patch) the thread's reply list
thread.messageOperations.updateConfig(patch) how the thread sends replies
channelPaginator.updateConfig(patch) one channel paginator your code created
client.threads.paginator.updateConfig(patch) the thread list
client.reminders.paginator.updateConfig(patch) the reminder list
userGroupPaginator.updateConfig(patch) one user group list your code created
client.threads.updateConfig(patch) the thread manager
client.reminders.updateConfig(patch) the reminder manager
client.notifications.updateConfig(patch) notifications
client.messageDeliveryReporter.updateConfig(patch) delivery receipts
client.connectionRecovery.updateConfig(patch) connection recovery
client.wsConnection.updateConfig(patch) the WebSocket connection
client.networkConnection.updateConfig(patch) the network status observer
liveLocationManager.updateConfig(patch) the live location manager your code created
searchController.updateConfig(patch) the search controller your code created

A channel has no updateConfig(), so a channel's own settings, such as typing and read events, can be set only for every channel at once, in client.config.

Setup functions

Some customizations aren't values: adding composer middleware, or a setting that depends on the object. A setup function covers those. It's code that runs for every object of its kind, the existing ones and every one created later. It receives the object, and can change it, for example with MessageComposer.updateConfig():

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  // Composers in threads don't send typing events. Channel composers keep the default.
  if (!composer.threadId) return;
  composer.updateConfig({ text: { publishTypingEvents: false } });
});
  • There is one setup function per kind: channel, thread, messageComposer, client, liveLocationManager and searchController. Setting another one replaces it, and null removes it.
  • It runs again whenever the settings of its kind change, so it can run more than once for the same object.
  • An error it throws is logged and doesn't break the object.

Teardown functions

A setup function can return a teardown function, which undoes what the setup function did. The teardown runs:

  • before the setup function runs again for the same object;
  • when you replace or remove the setup function, including with client.config.reset();
  • when the object is no longer used, whatever its kind: a channel when it's disposed, a composer when the UI stops showing it, and so on.

Return one when running the setup function twice would do something twice. For example, a setup function that listens to an event adds a new listener on every run, so it returns the function that removes it:

client.config.setSetupFunction("channel", ({ channel }) => {
  const { unsubscribe } = channel.on("message.new", (event) => {
    console.log(`New message in ${channel.cid}`, event.message?.id);
  });
  return unsubscribe;
});

Middleware inserted without unique: true is the same: every run adds another copy. The teardown removes it by its id:

import { voteActionsMiddleware } from "./voteActionsMiddleware";

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  composer.compositionMiddlewareExecutor.insert({
    middleware: [voteActionsMiddleware],
    position: { after: "stream-io/message-composer-middleware/attachments" },
  });
  return () =>
    composer.compositionMiddlewareExecutor.remove(voteActionsMiddleware.id);
});

A setup function that gives the same result however often it runs doesn't need one. Calling updateConfig() is like that, and so is inserting middleware with unique: true, which replaces the copy an earlier run inserted:

import { voteActionsMiddleware } from "./voteActionsMiddleware";

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  composer.compositionMiddlewareExecutor.insert({
    middleware: [voteActionsMiddleware],
    position: { after: "stream-io/message-composer-middleware/attachments" },
    unique: true,
  });
});

Without a teardown, that middleware stays on the existing composers when you replace or remove the setup function, or call client.config.reset(). If it should go then, return a teardown that calls composer.compositionMiddlewareExecutor.remove(voteActionsMiddleware.id).

Composer middleware shows a setup function that adds middleware, with its teardown.

How the settings combine

Each object puts its settings together from everything above, in this order. A later step wins for the fields it sets:

  1. The SDK's defaults.
  2. Your settings in client.config.
  3. Settings passed to the constructor of an object your code creates.
  4. The setup function.
  5. updateConfig() calls, including the ones a setup function makes.
  6. The server's limits, last, so nothing above can go beyond them.

The object puts them together again whenever one of these changes: when your code calls client.config.set(), client.config.setConfig(), client.config.reset() or client.config.setSetupFunction(), and, for channels and composers, when the channel's settings arrive from the server.

So a setting you change on one object with updateConfig() can be undone. Composers keep these changes, except after client.config.reset(); other objects don't. For a change that must last, use a setup function: it runs again every time the settings are put together.

Settings read once

A few settings are read only when an object is created. Setting them later doesn't change the objects that exist already, and the client logs a warning:

  • axiosRequestConfig, baseURL and notificationManager under the client key: pass them to the StreamChat constructor.
  • initialCursor, initialOffset and unreadReferencePolicy of a message list, and initialCursor and initialOffset of the pinned-messages list: set them before the channels and threads are created, next to StreamChat.getInstance().

Two composer settings are also read only when a composer is built, but without a warning: linkPreviews.debounceURLEnrichmentMs and location.getDeviceId. Set them before the composers are created.

Server settings

These live on the server. The client receives them with the channel or with the user.

  • Channel type settings, set in the Stream Dashboard or through the server API: typing_events, read_events, replies, user_message_reminders, delivery_events, uploads, url_enrichment, shared_locations, polls, max_message_length and commands. A channel's own config_overrides can narrow them for that channel alone. They arrive with the channel and are in channel.serverConfig, which is undefined until the channel has been queried or watched.
  • Capabilities: what the current user may do in each channel, from their roles and the channel type's permissions. They're in channel.state as ownCapabilities, a list such as "send-message", "upload-file" and "send-reaction". When they change, channel.state updates. After a channel query or Channel.updatePartial() returns different capabilities, the client also raises capabilities.changed; the server doesn't send it.
  • Per-channel settings stored with the channel, such as slow mode: channel.data.cooldown, the seconds between two messages. A user with the "skip-slow-mode" capability isn't held back.
  • The user's privacy settings for typing indicators and delivery receipts, in client.user.privacy_settings. When the user turns one off, the client doesn't send those events for them.

Who decides what

Some settings belong to the client alone, some to the server alone, and some need both sides to allow them.

Only the client:

  • how message lists behave (messagePaginator): page size, update throttling, the memory cap and the unread policy;
  • how messages are sent: the request handlers (channel.requestHandlers and thread.requestHandlers), and how many failed sends are kept for retry, and for how long (messageOperations);
  • composer features: drafts, sending before uploads finish, a custom CDN or upload function, the accepted files and their number, how links are found and how long to wait before fetching a preview, the location device id and the shortest sharing time, command validation, the default text, and retainCompositionOnSubmit;
  • timing for the connection, recovery, notifications and reminders;
  • search.

Only the server: capabilities, slow mode, the user's privacy settings, and the list of commands a channel offers, which is in channel.config.availableCommands.

Both sides must allow it. The server can only narrow what the client turns on, so a feature is on when both say yes:

Client setting Server setting
channel.typingEvents.enabled typing_events
channel.readEvents.enabled read_events
channel.replies.enabled replies
channel.userMessageReminders.enabled user_message_reminders
channel.deliveryEvents.enabled delivery_events
messageComposer.attachments.enabled uploads, unless attachments.customCdn is true
messageComposer.linkPreviews.enabled url_enrichment
messageComposer.location.enabled shared_locations
messageComposer.polls.enabled polls
messageComposer.text.maxLengthOnSend and maxLengthOnEdit max_message_length, which caps both

The value in effect is on each object: see Checking whether a feature is on.

Checking whether a feature is on

Every configurable object, the channel, the thread, the composer, the paginators and the client's managers, has the same members for its settings:

  • config: read it to get the settings in effect, the server's limits included.
  • configState: subscribe to it to follow changes, for example when a channel's server settings arrive with its first query.
  • updateConfig(patch): changes this object only, see Settings for one object. A channel doesn't have it.

The managers inside a composer, such as attachmentManager and linkPreviewsManager, read their part of the composer's settings, so follow them through the composer's configState.

Don't check client.config or channel.serverConfig for this: client.config holds only what your app set, and serverConfig only what the server allows. Only the object's config combines both.

For example, to show an attach button only while uploads are allowed:

const composer = channel.messageComposer;
const attachButton = document.querySelector<HTMLButtonElement>("#attach");

// Now
const canAttach = composer.config.attachments.enabled;

// From now on, whenever it changes
const unsubscribe = composer.configState.subscribeWithSelector(
  ({ attachments }) => ({ enabled: attachments.enabled }),
  ({ enabled }) => {
    if (attachButton) attachButton.hidden = !enabled;
  },
);

Some features also need a capability. To decide whether to offer polls, check the composer's setting and the user's capability:

const canCreatePolls =
  channel.messageComposer.config.polls.enabled &&
  channel.state.getLatestValue().ownCapabilities.includes("send-poll");

Used in