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

MessageComposer API

This page lists the API of MessageComposer in stream-chat and of the parts it's built from: the text composer, the attachment manager, the link previews manager, the location composer, the poll composer and the custom data manager.

What a composer does, and how sending, uploads, drafts and editing work, is explained on Message composer. Its middleware chains are on Composer middleware, and its settings on Configuration tree.

Best practices

  • Subscribe to a part's state to follow it. Getters such as textComposer.text or composer.quotedMessage return the value at the moment you read them, and don't tell you when it changes.
  • Change state through the methods below. Writing to a store directly skips the checks and side effects the methods run, such as cancelling uploads or applying a command.
  • Check what MessageComposer.send() and MessageComposer.update() return. They never throw: they resolve to 'sent', 'nothing-to-send' or 'failed'.
  • Configure every composer through client.config. Settings made on one composer with MessageComposer.updateConfig() or a setter stay on that composer, and uploads always use the channel composer's upload settings.
  • Change behaviour with middleware. Adding, replacing or removing a step of a chain is the supported way to change how text, uploads and messages are processed.
  • Inside a thread, use the thread's composer, thread.messageComposer, not the channel's.
const unsubscribe = composer.textComposer.state.subscribeWithSelector(
  ({ text }) => ({ text }),
  ({ text }) => console.log(`${text.length} characters`),
);

MessageComposer class architecture

A MessageComposer coordinates its parts. Each part keeps its data in a store of its own:

graph TD
    MC[MessageComposer] --> TC[textComposer]
    MC --> AM[attachmentManager]
    MC --> LPM[linkPreviewsManager]
    MC --> LC[locationComposer]
    MC --> PC[pollComposer]
    MC --> CDM[customDataManager]
Part Class Holds
textComposer TextComposer The text, the cursor, mentions, the active command, suggestions and typing events.
attachmentManager AttachmentManager Attachments other than link previews, and their uploads.
linkPreviewsManager LinkPreviewsManager Previews for the links in the text.
locationComposer LocationComposer A location to share.
pollComposer PollComposer The poll being created.
customDataManager CustomDataManager Data of your own, sent with the message or kept in the composer.

Getting a composer

Composer Where it comes from contextType
Channel channel.messageComposer 'channel'
Thread thread.messageComposer 'thread'
Edit new MessageComposer(...) 'message'

The channel and thread composers are created with their channel or thread. Build a composer yourself only to edit a message outside them:

import { MessageComposer } from "stream-chat";

const editComposer = new MessageComposer({
  client,
  compositionContext: message,
  composition: message,
});

The constructor takes MessageComposerOptions:

Option Type What it is
client StreamChat The client.
compositionContext Channel, Thread or LocalMessage What the composer belongs to. A message must carry its cid, or the constructor throws.
composition DraftResponse, MessageResponse or LocalMessage Optional. The content to start with: a draft, or the message to edit.
config DeepPartial<MessageComposerConfig> Optional. Settings for this composer, above client.config and below MessageComposer.updateConfig().

A composer starts with an empty state, with a draft, or with a message to edit:

  • Empty. The composer has a new id, which becomes the id of the message it sends.
  • A draft. The content is loaded, and draftId is the draft's id. The message gets a new id.
  • A message to edit. The content is loaded, editedMessage is the message, and id is the message's id. Drafts aren't saved and commands can't be used while editing.

Composer state

Store Type What it holds
state StateStore<MessageComposerState> The composer's own fields, in the table below.
editingAuditState StateStore<EditingAuditState> lastChange: { draftUpdate, stateUpdate }: when the content and the draft last changed, in nanoseconds.
configState StateStore<MessageComposerConfig> The settings in effect. config reads the same value once.
state field Type What it holds
id string The id of the message the composer will send.
draftId string or null The id of the loaded or saved draft.
pollId string or null The poll created with MessageComposer.createPoll(), which the next message carries.
quotedMessage LocalMessage or null The message being replied to.
showReplyInChannel boolean Whether a thread reply is also shown in the channel.
editedMessage LocalMessage or null The message being edited, as it was when the edit began. MessageComposer.restore() goes back to it.

Getters

The getters of MessageComposer return the value at the moment you read them, and don't tell you when it changes.

Getter Returns
id, draftId, pollId The state fields of those names.
quotedMessage The quotedMessage field.
showReplyInChannel The showReplyInChannel field.
editedMessage The edited message, or undefined. Has a setter, which writes the field.
lastChange editingAuditState.lastChange.
lastChangeOriginIsLocal Whether the content changed here since it was loaded, or was written from scratch.
config The settings in effect, after the server's limits.
requestedConfig The settings asked for, before the server's limits.
contextType 'channel', 'thread' or 'message'.
tag contextType and the context's id, such as channel_general.
threadId The thread's id for a thread composer, the parent id for an edit of a reply, otherwise null.
channel The composer's channel. For an edit composer, the channel from the message's cid.
client The channel's client.
compositionContext What the constructor received as compositionContext.
defaultSubmitTarget Where MessageComposer.send() and MessageComposer.update() go: the thread for a thread composer, the reply's thread when client.threads has it, else the channel.
hasSendableData Whether there is something to send. A running upload blocks it, unless attachments.pendingUploadsEnabled is on.
contentIsEmpty Whether text, attachments, poll and location are all empty.
compositionIsEmpty contentIsEmpty, and no quoted message either.
allowsPendingUploads attachments.pendingUploadsEnabled.
retainsCompositionOnSubmit What retainCompositionOnSubmit answers for the current content.
isCommandSendable Whether the active command, if there is one, can be sent.

Methods

Sending

Method Returns What it does
send() Promise<MessageComposerSubmitResult> Composes the message, clears the composer unless retainCompositionOnSubmit keeps the content, and sends it through the target's message operations. Never throws.
update() Promise<MessageComposerSubmitResult> The same for an edit: saves editedMessage with the new content. Never throws.
compose() Promise<MessageComposerMiddlewareState | undefined> Runs the composition chain and returns { message, localMessage, sendOptions }, or undefined when a step discarded it. Sends nothing.
sendLocation() Promise<void> Sends the location in locationComposer as a message of its own. Does nothing in a thread or without a valid location. Throws when the request fails.

MessageComposerSubmitResult is 'sent', 'nothing-to-send' or 'failed'. Message composer explains each one.

const result = await composer.send();

if (result === "failed") {
  // The message is in the list as failed, and the composer is already empty.
}

State

Method What it does
initState({ composition }?) Loads a draft or a message into every part, or empties them without composition.
initStateFromChannelResponse(response) Loads the draft a channel query returned, or clears a draft that's gone.
clear() Empties the composer, the quoted message included, and leaves editing.
restore() Puts editedMessage back into the composer, or clears it when nothing is being edited.
refreshId() Gives the composer a new id.
setQuotedMessage(message | null) Sets or removes the quoted message. A command the quote disables is cleared.
setEditedMessage(message | null | undefined) Sets the edited message without loading it, and clears the active command.
toggleShowReplyInChannel() Flips showReplyInChannel.

Drafts. MessageComposer.createDraft(), MessageComposer.deleteDraft() and MessageComposer.getDraft() do nothing while drafts.enabled is off or a message is being edited. The first two also do nothing for a channel that has no id yet.

Method Returns What it does
createDraft() Promise<void> Runs the draft chain and saves the result as the draft. Skips an empty draft or one that didn't change.
deleteDraft() Promise<void> Deletes the saved draft.
getDraft() Promise<void> Loads the channel's or thread's draft into the composer.
composeDraft() Promise<{ draft } | undefined> Runs the draft chain only.

Polls

Method Returns What it does
createPoll() Promise<void> Creates the poll from pollComposer on the server and sets pollId. Throws when the request fails, after publishing an api:poll:create:failed notification.

Commands

Method Returns What it does
getCommandDisabledReason(command) 'editing', 'quoted_message' or undefined Why a command can't be used now: while editing, or a moderation command with a quote.
isCommandDisabled(command) boolean Whether getCommandDisabledReason returns a reason.
validateCommandSendability(command, text?) CommandSendability Asks commands.sendValidator whether the command can be sent with the text.

Configuration

Method What it does
updateConfig(patch) Changes this composer's settings. The change is kept when the settings are rebuilt, except by client.config.reset().
initializeConfig() Rebuilds the settings from client.config and drops updateConfig() changes. client.config.reset() calls it.

Effects and snapshots. The command chips use these, and Effect handlers explains them.

Method What it does
registerEffectHandler(type, handler) Runs handler(effect, composer) for effects of that type. Replaces the handler for a built-in type.
applyEffects(effects) Runs the handlers for a list of effects.
getSnapshot() Returns the state of every part.
restoreSnapshot(snapshot) Puts a snapshot back into every part.
captureSnapshot(snapshot?) Keeps a snapshot, unless one is kept already.
popSnapshot() Returns the kept snapshot and forgets it.
clearSnapshots() Forgets the kept snapshot.

Subscriptions

Method What it does
registerSubscriptions() Starts the composer's event handling and its setup function, and returns a function that stops them. The UI layer calls it while it shows the composer.
unregisterSubscriptions() Stops them.
registerDraftEventSubscriptions() Starts following draft.updated and draft.deleted, and returns a function that stops. The composer calls it while drafts are on.

Static members

Member What it does
MessageComposer.generateId() Returns a new message id.
MessageComposer.evaluateContextType(context) Returns 'channel', 'thread' or 'message' for a context.
MessageComposer.constructTag(context) Returns the tag a composer for that context would have.

Events

While its subscriptions run, a composer reacts to these:

Event What happens
message.updated, reaction.new, reaction.updated, reaction.deleted For the message being edited, the composer reloads it from the event, and unsaved changes are lost. For the quoted message, the quote is updated.
message.deleted For the message being edited, the composer is cleared. For the quoted message, the quote is removed.
draft.updated, draft.deleted With drafts on and no edit in progress, the composer loads the draft in the event, or clears itself when the draft is deleted.

The composer also rebuilds its settings when client.config or its setup function changes and when the channel's server settings arrive. When the content becomes empty, it deletes the draft. When the text changes, it looks for links to preview.

Text composition with suggestions

composer.textComposer is a TextComposer. Its state holds:

Field Type What it holds
text string The text.
selection { start, end } The cursor, or the selected range.
mentions MentionEntity[] The mentions in the text: users, @channel, @here, roles and user groups. Mentions no longer in the text are left out of the message.
mentionedUsers UserResponse[] Deprecated. The user mentions only. Use mentions.
command Command or null The active slash command, kept apart from the text.
suggestions Suggestions or undefined The open suggestions: { query, searchSource, trigger }.
typing Record<string, Event> The users typing, by user id: each one's latest typing event. It can include the current user. Kept on the channel's composer.

A MentionEntity has an id and a mentionType: 'user' (with the user's fields), 'channel', 'here', 'role' or 'user_group'. Suggestions explains how mentions and commands are found.

Getters and setters

text, selection, mentions, mentionedUsers, command, suggestions and typing read the state. textIsEmpty says whether the text is empty or only Markdown markers. config is composer.config.text, channel the composer's channel, and middlewareExecutor the text chain.

These read the composer's text settings and write them with MessageComposer.updateConfig(), for this composer only:

Getter and setter Setting
enabled text.enabled
defaultValue text.defaultValue
maxLengthOnEdit text.maxLengthOnEdit
maxLengthOnSend text.maxLengthOnSend
publishTypingEvents text.publishTypingEvents

Methods

Method What it does
handleChange({ text, selection }) The input path. Runs the text chain, which cuts the text to maxLengthOnEdit and finds mentions and commands, then sends a typing event.
handleSelect(suggestion) Runs the text chain for a picked suggestion, which inserts the mention or sets the command.
insertText({ text, selection? }) Inserts at selection, or at the cursor, and goes through handleChange().
wrapSelection({ head?, tail?, selection? }) Puts head before and tail after the selection, for example ** for bold. Runs no middleware.
setText(text) Sets the text without running any middleware.
setSelection(selection) Sets the cursor or the selected range.
setCommand(command | null) Makes a command active, unless the composer disables it. null clears it.
clearCommand() Clears the active command and restores what the composer held before it was picked.
upsertMentionEntity(entity) Adds a mention, or replaces the one with the same mentionType and id.
getMentionEntity(mentionType, id) Returns a mention, or undefined.
removeMentionEntity(mentionType, id) Removes a mention.
setMentions(entities) Replaces all mentions.
setMentionedUsers(users) Replaces the user mentions and keeps the others.
setSuggestions(suggestions) Opens suggestions: { query, searchSource, trigger }.
closeSuggestions() Closes them.
setTyping(typing), setTypingEvent(userId, event), removeTypingEvent(userId) Write the typing field. The channel calls setTyping() on typing events.
initState({ message }?) Loads a message's text and mentions, or the default text without one.
getSnapshot(), restoreSnapshot(snapshot) Read and put back the whole state.

TextComposer.upsertMentionedUser(user), TextComposer.getMentionedUser(userId) and TextComposer.removeMentionedUser(userId) are deprecated. Use the mention-entity methods with mentionType: 'user'.

Pass a selection to TextComposer.insertText() and TextComposer.wrapSelection(), or keep the cursor current with TextComposer.setSelection(): without one they use textComposer.selection.

textIsEmpty(text), exported from stream-chat, is the check textIsEmpty uses.

Attachment management

composer.attachmentManager is an AttachmentManager. Its state has one field, attachments, a LocalAttachment[].

Every local attachment has a localMetadata object, which is removed before the message is sent:

Field What it holds
id The attachment's local id, which the methods below take.
file The File or FileReference to upload.
uploadState 'pending', 'uploading', 'finished', 'failed' or 'blocked'.
uploadProgress 0 to 100 while uploading, when attachments.trackUploadProgress is on.
uploadConfirmationPending Every byte is sent and the server hasn't answered yet.
uploadPermissionCheck { uploadBlocked, reason? }: why a file was blocked by the app's upload settings.
previewUri A local URL for previewing the file.

An attachment loaded from a draft or an edited message has only an id and, for an uploaded file, uploadState: 'finished'.

Getters

Getter Returns
attachments The attachments.
attachmentsById The attachments by local id.
isUploadEnabled Whether a file can be added now: attachments are enabled, a slot is free, and the user has upload-file unless customCdn is on.
availableUploadSlots, hasAvailableUploadSlots How many more files fit under maxNumberOfFilesPerMessage, and whether any do.
successfulUploads The finished uploads.
successfulUploadsCount, uploadsInProgressCount, pendingUploadsCount, failedUploadsCount, blockedUploadsCount The number of attachments in each upload state.
hasUploadPermission Whether the user has the upload-file capability in the channel.
usesStreamStorage true unless attachments.customCdn is on.
hasCustomDoUploadRequest Whether attachments.doUploadRequest is set.
config composer.config.attachments.
client, channel The composer's client and channel.

acceptedFiles, maxNumberOfFilesPerMessage and the deprecated fileUploadFilter are getters and setters for the settings of those names, on this composer only. preUploadMiddlewareExecutor and postUploadMiddlewareExecutor are the upload chains.

Methods

Method Returns What it does
uploadFiles(files) Promise<LocalUploadAttachment[] | undefined> Adds and uploads as many files as there are free slots. Does nothing when isUploadEnabled is false.
uploadFile(file) Promise<LocalUploadAttachment> Adds and uploads one file, through the pre-upload and post-upload chains. Doesn't check isUploadEnabled.
upsertAttachments(attachments) void Adds attachments, or merges them into the ones with the same local id.
updateAttachment(attachment) void Merges changes into an existing attachment.
removeAttachments(localIds) void Removes attachments and cancels their uploads.
clearAttachments() void Removes every attachment.
setAttachments(attachments) void Replaces the list without any checks.
getAttachmentIndex(localId) number The attachment's position, or -1.
getUploadsByState(state) LocalAttachment[] The attachments in one upload state.
cancelAttachmentUploads(attachments?) void Cancels the uploads of the given attachments, or of all.
setCustomUploadFn(fn) void Sets attachments.doUploadRequest for this composer.
doUploadRequest(file, options?) Promise<MinimumUploadRequestResult> Uploads with doUploadRequest when one is set, else with Stream.
doDefaultUploadRequest(file, options?) Promise<MinimumUploadRequestResult> Uploads with Stream, as an image or a file.
getUploadConfigCheck(file) Promise<UploadPermissionCheckResult> Checks a file against the app's upload settings: allowed and blocked types, and the size limit.
initState({ message }?) void Loads a message's attachments, or empties the list.
getSnapshot(), restoreSnapshot(snapshot) AttachmentManagerSnapshot, void Read and put back the list. Restoring cancels the uploads of the current list.

AttachmentManager.toLocalUploadAttachment(file) is a static method that turns a File, Blob or FileReference into a pending local attachment with an id and a preview URL. AttachmentManager.fileToLocalUploadAttachment() and AttachmentManager.uploadAttachment() are deprecated: use AttachmentManager.toLocalUploadAttachment() and AttachmentManager.uploadFile().

Custom uploads

Uploads to your own storage are set up with attachments.doUploadRequest and attachments.customCdn, as Message composer shows. To choose which files are taken, add a pre-upload middleware that calls discard() for the ones to leave out.

The pre-upload chain also checks each file against the app's upload settings in Stream, even with customCdn on. Storage of your own may accept files Stream wouldn't. To skip that check, remove its middleware in the composer setup function. A key has one setup function, so put this in the same one as the rest of your composer setup:

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  composer.attachmentManager.preUploadMiddlewareExecutor.remove(
    "stream-io/attachment-manager-middleware/file-upload-config-check",
  );
});

Attachment identity functions

stream-chat exports type guards for attachments. An attachment is local when its localMetadata has an id.

import { isLocalImageAttachment } from "stream-chat";

const firstImage = attachmentManager.attachments.find(isLocalImageAttachment);
console.log(firstImage?.original_width);
Function Checks that the attachment is
isLocalAttachment local
isLocalUploadAttachment local, with upload metadata
isPendingUpload local, with uploadState 'pending' or 'uploading'
isFinishedUpload local, with uploadState: 'finished'
isFailedUpload local, with uploadState: 'failed'
isFileAttachment a file
isLocalFileAttachment a local file
isImageAttachment an image
isLocalImageAttachment a local image
isAudioAttachment audio
isLocalAudioAttachment local audio
isVoiceRecordingAttachment a voice recording
isLocalVoiceRecordingAttachment a local voice recording
isVideoAttachment a video
isLocalVideoAttachment a local video
isUploadedAttachment an uploaded file: audio, file, image, video or voice recording
isScrapedContent a link preview: it has og_scrape_url or title_link
isGiphyAttachment a Giphy
isSharedLocationResponseData a shared location from a message

composer.linkPreviewsManager is a LinkPreviewsManager. Its state has one field, previews, a Map from URL to LinkPreview. A LinkPreview is the preview's Open Graph data with a status, a LinkPreviewStatus:

Status Value Meaning
PENDING 'pending' Not loaded yet.
LOADING 'loading' The preview is being fetched.
LOADED 'loaded' Fetched. Loaded previews are sent as attachments of the message.
FAILED 'failed' The fetch failed.
DISMISSED 'dismissed' The user removed it. It isn't fetched again while the URL stays in the text.

Getters

previews reads the map. loadingPreviews, loadedPreviews, failedPreviews, dismissedPreviews and pendingPreviews return the previews in each status, as arrays. config is composer.config.linkPreviews.

enabled, debounceURLEnrichmentMs, findURLFn and onLinkPreviewDismissed are getters and setters for the settings of those names, on this composer only.

Methods

Method What it does
findAndEnrichUrls(text) Finds the URLs in the text and fetches their previews, after debounceURLEnrichmentMs. The composer calls it when the text changes.
cancelURLEnrichment() Cancels a scheduled fetch.
updatePreview(url, data) Merges data into a preview, or adds one.
dismissPreview(url) Marks a preview dismissed and calls onLinkPreviewDismissed.
clearPreviews() Removes every preview except the dismissed ones. The composer calls it when the text is emptied.
initState({ message }?) Loads a message's previews, or empties the map.
getSnapshot(), restoreSnapshot(snapshot) Read and put back the state.

The static methods LinkPreviewsManager.previewIsLoading(), LinkPreviewsManager.previewIsLoaded(), LinkPreviewsManager.previewIsFailed(), LinkPreviewsManager.previewIsDismissed() and LinkPreviewsManager.previewIsPending() check a preview's status. LinkPreviewsManager.getPreviewData(preview) returns the preview without its status, as it's sent.

Location composition

composer.locationComposer is a LocationComposer. Its state has one field, location: the location to share, or null. It holds latitude, longitude, created_by_device_id and message_id, and either durationMs for a live location being composed or end_at for one loaded from a message.

Member What it is
location Getter: the location field.
validLocation Getter: the location as it's sent, or null when it's incomplete or its durationMs is under location.minShareDurationMs. A durationMs becomes an end_at.
deviceId Getter: the device id, from location.getDeviceId when the composer was built.
config Getter: composer.config.location.
setData({ latitude, longitude, durationMs? }) Sets the location, with this composer's id and the device id. Does nothing while location.enabled is off. With durationMs, the location is live for that long.
initState({ message }?) Loads a message's location, or clears it.
getSnapshot(), restoreSnapshot(snapshot) Read and put back the state.

A latitude or longitude of exactly 0 counts as missing: LocationComposer.setData() ignores it, and validLocation is null.

To send a location on its own, call MessageComposer.sendLocation(). A location set in locationComposer also goes out with the next MessageComposer.send().

Poll composition

composer.pollComposer is a PollComposer. Its state holds the poll form and its errors:

Field Type What it holds
data.id string The poll's id.
data.name string The question.
data.description string A description.
data.options { id, text }[] The options. Starts with one empty option.
data.max_votes_allowed string Votes per user, as typed. Empty means no limit.
data.enforce_unique_vote boolean One vote per user. true by default.
data.allow_answers boolean Users can add free-text answers.
data.allow_user_suggested_options boolean Users can add options.
data.voting_visibility VotingVisibility 'public' by default.
errors PollComposerFieldErrors Errors by field, and option errors by option id. Each has a stable code, such as validation:poll:name:required, and an English message.
Member What it does
updateFields(data, injectedFieldErrors?) Changes fields and validates them through the poll state chain. options takes the whole list, or { index, text } for one option. Errors you pass win over the computed ones.
handleFieldBlur(field) Validates one field, as when the user leaves it.
compose() Runs the poll composition chain and returns { data, errors } in the form the server takes, or undefined when the poll can't be created.
canCreatePoll Getter: whether there's a question, at least one non-empty option, a valid max_votes_allowed and no errors.
initState() Resets the form.
getSnapshot(), restoreSnapshot(snapshot) Read and put back the state.

The getters id, name, description, options, max_votes_allowed, enforce_unique_vote, allow_answers, allow_user_suggested_options and voting_visibility read the fields of data. stateMiddlewareExecutor and compositionMiddlewareExecutor are the poll's chains, described on Composer middleware.

To create the poll, call MessageComposer.createPoll(), then MessageComposer.send() to send the message that carries it.

Custom data management

composer.customDataManager is a CustomDataManager. Its state holds two objects:

Field Type What it holds
message CustomMessageData Fields added to the top level of the outgoing message.
custom CustomMessageComposerData Data kept in the composer and never sent, such as a flag your UI reads.

Type both by augmenting the CustomMessageData and CustomMessageComposerData interfaces of stream-chat, as Data conventions explains.

Member What it does
customMessageData, customComposerData Getters: the message and custom fields.
setMessageData(data) Merges fields into message.
setCustomData(data) Merges fields into custom.
isMessageDataEqual(next, previous?) Decides whether message changed, which counts as a change of the content, for drafts too. Compares the JSON of both by default.
initState({ message }?) Empties both objects. They aren't restored from a draft or an edited message.
getSnapshot(), restoreSnapshot(snapshot) Read and put back the state.

CustomDataManager.setMessageData() writes to the top level of the message, not to message.custom. To send message.custom, add a composition middleware, as Composer middleware shows.

isMessageDataEqual is a property, so a setup function can replace it on each composer. This one compares the top-level values one by one, so the order of the keys doesn't matter, as it does for the default JSON comparison:

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  composer.customDataManager.isMessageDataEqual = (next, previous) => {
    const nextData: Record<string, unknown> = { ...next.message };
    const previousData: Record<string, unknown> = { ...previous?.message };
    const keys = new Set([
      ...Object.keys(nextData),
      ...Object.keys(previousData),
    ]);
    return [...keys].every((key) => nextData[key] === previousData[key]);
  };
});

Additional low-level exports

stream-chat exports more of the composer than the classes above. These are useful for custom uploads, suggestions and middleware:

Group Notable exports Use them when
Configuration MessageComposerConfig, AttachmentManagerConfig, TextComposerConfig, LinkPreviewsManagerConfig, LocationComposerConfig, PollComposerConfig, UploadRequestFn, MinimumUploadRequestResult, DEFAULT_COMPOSER_CONFIG and the per-part defaults, defaultCommandSendabilityValidator You type a custom upload request or command validator, or read the SDK's defaults.
Middleware TextComposerMiddlewareExecutor, MessageComposerMiddlewareExecutor, MessageDraftComposerMiddlewareExecutor, AttachmentPreUploadMiddlewareExecutor, AttachmentPostUploadMiddlewareExecutor, PollComposerCompositionMiddlewareExecutor, PollComposerStateMiddlewareExecutor, and the create*Middleware factories You replace or extend one step of a chain. Composer middleware lists the built-in ones.
Search sources MentionsSearchSource, CommandSearchSource You change how mentions or commands are searched.
Attachments and files The identity functions above, isFileReference, isFileList, isImageFile, createFileFromBlobs, readFileAsArrayBuffer, generateFileName, getAttachmentTypeFromMimeType, ensureIsLocalAttachment, getAttachmentPreviewUrl You build custom drag and drop, paste, upload or attachment previews.
Polls POLL_COMPOSER_VALIDATION_CODE, createPollComposerStateMiddleware, pollStateChangeValidators, MAX_POLL_OPTIONS You localize poll errors or add validation of your own.
Types LocalAttachment, LocalUploadAttachment, FileReference, UploadPermissionCheckResult, TextComposerSuggestion<T>, MentionSuggestion, MentionEntity, UserSuggestion, ChannelMentionSuggestion, HereMentionSuggestion, RoleMentionSuggestion, UserGroupMentionSuggestion, CustomTextComposerSuggestion You type custom attachments, mentions, suggestions or middleware state.

CustomTextComposerSuggestion is an interface: augment it in stream-chat to type the fields of your own suggestion items, for example interface CustomTextComposerSuggestion { label?: string } inside declare module "stream-chat". Declare the fields optional, because every suggestion type in the app shares the interface.