# Attachments and uploads

> **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/).

Attachments are the files, images and videos a message carries. This page covers how a user adds them in the composer, how they're uploaded, and how the message list shows them.

## What users get

The user opens the attachment menu with the + button before the input and picks File. They select two photos and a PDF. Each file shows above the input at once, with its upload progress. They remove one photo with its ✕ and send. In the message list, the photo shows as an image they can open in a gallery, and the PDF as a row with its name, size and a download button.

They can also drop files on the composer or paste them into the input. A file whose upload fails shows a retry button. A file the app's upload settings refuse isn't uploaded: a file row shows "Upload blocked", or "File too large" when it's over the size limit, and an image or video shows a warning icon.

## How it works across the layers

- **State.** Each composer has an attachment manager, `messageComposer.attachmentManager`. Its store holds the attachments of the message being written, each with an upload state: `pending`, `uploading`, `finished`, `failed` or `blocked`. Sending puts the finished uploads into the message.
- **UI.** In the composer, `AttachmentSelector` opens the file picker, `AttachmentPreviewList` shows the files and their progress, and `WithDragAndDropUpload` takes dropped files. In the message list, `Attachment` renders what a message carries.
- **Layout.** Not involved.

### Adding and uploading a file

>
> **Info:** **Example**
>
> Take the user's PDF:
>
> 1. The file input, a drop or a paste calls `AttachmentManager.uploadFiles(files)`. Files beyond `attachments.maxNumberOfFilesPerMessage` (10 by default) are left out.
> 2. The pre-upload middleware checks the file against the app's upload settings: allowed and blocked file types, and the size limit. A refused file stays in the composer as `blocked`, and a `validation:attachment:upload:blocked` notification is published.
> 3. The file uploads through `doUploadRequest`: Stream's CDN by default, or your own upload function. Its state is `uploading`, with `localMetadata.uploadProgress` from 0 to 100.
> 4. The post-upload middleware sets the attachment's URL and thumbnail from the response. A failed upload becomes `failed`, and an `api:attachment:upload:failed` notification is published.
> 5. On send, the composition middleware adds the finished attachments to the message. By default the message can't be sent while an upload is running. [Sending with pending uploads](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/sending-with-pending-uploads/) lets it go out at once.
>

The upload states, the custom upload function and the limits are explained on [Message composer](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/#attachments-and-uploads). The upload middleware and their ids are on [Composer middleware](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/composer-middleware/#uploads).

### In the composer

- **`AttachmentSelector`** opens a menu with File, Poll, Location and Commands. File clicks a hidden file input. Each entry is shown only when it's available: File when uploads are allowed, Poll and Location outside threads when they're turned on (Poll also needs the `send-poll` capability), Commands when the channel type has commands. When File is the only entry, the selector is a plain upload button. The selector is disabled during a slow-mode cooldown, and hidden while a command is active.
- **`AttachmentPreviewList`** shows each attachment with its progress, a ✕ to remove it and a retry button after a failure. Images and videos open in a gallery. Voice recordings, link previews and a shared location have previews of their own.
- **`WithDragAndDropUpload`** wraps the composer UI. While files are dragged over it, it shows "Drag your files here". It refuses the file types `attachments.acceptedFiles` doesn't list, and turns off during a slow-mode cooldown and when uploads aren't allowed.
- **Paste.** Pasting files into the input uploads them. Pasted text is inserted as text.

[Message composer UI](https://getstream.io/chat/docs/sdk/react/v15/ui/components/message-composer-ui/) shows where each part sits and which store it reads.

### In the message list

`Attachment` sorts a message's attachments into groups and renders each group with its own component. Each component is a prop of `Attachment`, so you can replace it:

| Attachment                                          | Rendered by                                                 |
| --------------------------------------------------- | ----------------------------------------------------------- |
| One image                                           | `Image`                                                     |
| One video                                           | `Media`                                                     |
| Several images or videos                            | `ModalGallery`                                              |
| A Giphy                                             | `Giphy`, with `AttachmentActions` for the preview's buttons |
| A link preview: has `og_scrape_url` or `title_link` | `Card`                                                      |
| A file, an audio file, a voice recording            | `File`, `Audio`, `VoiceRecording`                           |
| A shared location, from `message.shared_location`   | `Geolocation`                                               |
| Anything else                                       | `UnsupportedAttachment`                                     |

The default `MessageUI` renders `Attachment` for every message that has attachments or a shared location. A Giphy renders inline and doesn't open the gallery.

## Turning it on or off

Attachments are on by default. Two sides decide whether a user can upload:

- **The client:** `messageComposer.attachments.enabled`, `true` by default.
- **The server:** the channel type's `uploads` setting, and the user's `upload-file` capability.

To turn uploads off in every composer:

```ts
client.config.set({
  messageComposer: { attachments: { enabled: false } },
});
```

With `attachments.customCdn: true`, files go to storage Stream doesn't host, so the server's `uploads` setting and the `upload-file` capability don't apply, and `attachments.enabled` decides alone. [Configuration](https://getstream.io/chat/docs/sdk/react/v15/state/state-and-entities/configuration/#who-decides-what) lists every setting both sides decide.

`attachmentManager.isUploadEnabled` combines all of it with the free slots left in the message. The selector, the drop zone and `AttachmentManager.uploadFiles()` all follow it, so a UI of your own should read it too.

## Customizing

### Limit what users can attach

`acceptedFiles` sets which files the file picker offers and the drop zone takes. `maxNumberOfFilesPerMessage` caps the number per message:

```ts
client.config.set({
  messageComposer: {
    attachments: {
      acceptedFiles: ["image/*", "application/pdf"],
      maxNumberOfFilesPerMessage: 5,
    },
  },
});
```

`acceptedFiles` doesn't stop a pasted file or one added in code. To refuse a file whatever its source, add a pre-upload middleware. This one blocks videos larger than 50 MB, so the video stays in the composer as `blocked`, its preview shows the warning icon, and the built-in notification is published:

```ts
import type { AttachmentPreUploadMiddleware } from "stream-chat";

const MAX_VIDEO_BYTES = 50 * 1024 * 1024;

const videoSizeLimit: AttachmentPreUploadMiddleware = {
  id: "my-app/video-size-limit",
  handlers: {
    prepare: ({ forward, next, state }) => {
      const { attachment } = state;
      const { file } = attachment.localMetadata;
      if (!file.type.startsWith("video/") || file.size <= MAX_VIDEO_BYTES) {
        return forward();
      }

      return next({
        ...state,
        attachment: {
          ...attachment,
          localMetadata: {
            ...attachment.localMetadata,
            uploadPermissionCheck: {
              reason: "size_limit",
              uploadBlocked: true,
            },
            uploadState: "blocked",
          },
        },
      });
    },
  },
};

client.config.setSetupFunction("messageComposer", ({ composer }) => {
  const { preUploadMiddlewareExecutor } = composer.attachmentManager;

  preUploadMiddlewareExecutor.insert({
    middleware: [videoSizeLimit],
    position: {
      after: "stream-io/attachment-manager-middleware/file-upload-config-check",
    },
    unique: true,
  });

  return () => preUploadMiddlewareExecutor.remove(videoSizeLimit.id);
});
```

There's one setup function per kind of object, so add this to your app's composer setup function if it has one. [Composer middleware](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/composer-middleware/#adding-middleware) explains positions, `unique` and the teardown.

### Upload to your own storage

Register your own upload function as `attachments.doUploadRequest`, and set `customCdn: true` when the files never reach Stream. [Message composer](https://getstream.io/chat/docs/sdk/react/v15/state/writing-messages/message-composer/#attachments-and-uploads) has the example.

### Change the attachment menu

The menu is an action set: `defaultAttachmentSelectorActionSet`, passed to `AttachmentSelector` as `attachmentSelectorActionSet`. This selector leaves out Location:

```tsx
import {
  AttachmentSelector,
  Channel,
  defaultAttachmentSelectorActionSet,
  MessageComposer,
  MessageList,
  WithComponents,
} from "stream-chat-react";

const actionSet = defaultAttachmentSelectorActionSet.filter(
  (action) => action.type !== "addLocation",
);

const AttachmentSelectorWithoutLocation = () => (
  <AttachmentSelector attachmentSelectorActionSet={actionSet} />
);

const overrides = { AttachmentSelector: AttachmentSelectorWithoutLocation };

export const ChannelWithoutLocation = () => (
  <WithComponents overrides={overrides}>
    <Channel channel={channel}>
      <MessageList />
      <MessageComposer />
    </Channel>
  </WithComponents>
);
```

[Actions](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/actions/) shows how to add an action of your own, with a dialog. The `AttachmentSelector` props, such as `getModalPortalDestination` for where its dialogs open, are in the [Attachment Selector reference](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message-composer/attachment-selector/).

### Drop files on a larger area

The composer UI already takes dropped files. To take them anywhere in a larger area, such as the whole channel, wrap that area in `WithDragAndDropUpload`, as [Message composer UI](https://getstream.io/chat/docs/sdk/react/v15/ui/components/message-composer-ui/#drag-and-drop) shows. Three things to know:

- Files dropped on the wrapper go to every composer inside it. Wrap a channel and a thread separately, or a file dropped on one uploads to both.
- Only the outermost wrapper handles drops. A `WithDragAndDropUpload` inside another one, such as the composer's own, renders only its element.
- The wrapper gets the class `str-chat__dropzone-root`, which sets `position: relative` and `flex: 1`, so the overlay covers the wrapper. Add your own layout with `className` or `style`.

### Change how attachments render

Replace one renderer by passing it to `Attachment`, and register the result as the `Attachment` override. Here, attachments of a type of your own, `product`, get a card, and everything else renders as before. A `product` attachment reaches `UnsupportedAttachment` only without `og_scrape_url` and `title_link`, because either one makes it a link preview for `Card`:

```tsx
import type {
  AttachmentProps,
  UnsupportedAttachmentProps,
} from "stream-chat-react";
import {
  Attachment,
  Channel,
  MessageComposer,
  MessageList,
  UnsupportedAttachment,
  WithComponents,
} from "stream-chat-react";

const ProductOrUnsupported = ({ attachment }: UnsupportedAttachmentProps) => {
  if (attachment.type !== "product") {
    return <UnsupportedAttachment />;
  }

  return (
    <div className="product-card">
      {attachment.image_url && <img alt="" src={attachment.image_url} />}
      <span>{attachment.title}</span>
    </div>
  );
};

const CustomAttachment = (props: AttachmentProps) => (
  <Attachment {...props} UnsupportedAttachment={ProductOrUnsupported} />
);

const overrides = { Attachment: CustomAttachment };

export const ChannelWithProducts = () => (
  <WithComponents overrides={overrides}>
    <Channel channel={channel}>
      <MessageList />
      <MessageComposer />
    </Channel>
  </WithComponents>
);
```

`Attachment` also takes `imageAttachmentSizeHandler` and `videoAttachmentSizeHandler`, which pick the URL to load for an image or a video at its rendered size, and `giphyVersion`, the Giphy rendition to show. The CSS variable `--str-chat__attachment-max-width` sets the largest size of images and videos; it must resolve to pixels. To replace the previews in the composer, see the [Attachment previews recipe](https://getstream.io/chat/docs/sdk/react/v15/recipes/composer/attachment-previews/). [Overriding components](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/overriding-components/) explains how overrides apply.

## Reference

- [Attachments](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message/attachments/): the `Attachment` props.
- [Attachment Selector](https://getstream.io/chat/docs/sdk/react/v15/component-reference/message-composer/attachment-selector/): the `AttachmentSelector` props and `AttachmentSelectorContext`.
- [WithDragAndDropUpload](https://getstream.io/chat/docs/sdk/react/v15/component-reference/utilities/withdraganddropupload/): the drop zone's props.
- [MessageComposer API](https://getstream.io/chat/docs/sdk/react/v15/reference/state/messagecomposer-api/): the attachment manager's methods and store.
- [Configuration tree](https://getstream.io/chat/docs/sdk/react/v15/reference/state/configuration-tree/): every `messageComposer.attachments` setting.
- [Composer hooks](https://getstream.io/chat/docs/sdk/react/v15/reference/hooks/composer-hooks/): `useAttachmentManagerState` and the other composer hooks.
- [Sending with pending uploads](https://getstream.io/chat/docs/sdk/react/v15/features/messaging/sending-with-pending-uploads/), [Voice recordings and audio playback](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/voice-recordings-and-audio-playback/), [Link previews](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/link-previews/) and [Location sharing](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/location-sharing/): the attachments with features of their own.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/attachments-and-uploads/](https://getstream.io/chat/docs/sdk/react/v15/features/rich-content/attachments-and-uploads/).