# MessageComposer API

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

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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/). Its middleware chains are on [Composer middleware](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/composer-middleware/), and its settings on [Configuration tree](https://getstream.io/chat/docs/sdk/react/v15/reference/state/configuration-tree/#messagecomposer).

## 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.

```ts
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:

```mermaid
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:

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/) explains each one.

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/composer-middleware/#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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/#attachments-and-uploads) 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:

```ts
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`.

```ts
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                               |

## Link preview management

`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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/composer-middleware/#polls).

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](https://getstream.io/chat/docs/sdk/react/v15/state/state-and-entities/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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/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:

```ts
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](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/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.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/reference/state/messagecomposer-api/](https://getstream.io/chat/docs/sdk/react/v15/reference/state/messagecomposer-api/).