# Image and File Uploads

Stream Chat allows you to upload images, videos, and other files to the Stream CDN or your own CDN. Uploaded files can be used as message attachments, user avatars, or channel images.

<admonition type="info">

Stream's UI SDKs (React, React Native, Flutter, SwiftUI, Jetpack Compose, etc.) handle file uploads automatically through their message composer components. The upload process, progress tracking, and attachment handling are built into these components. Use the methods described on this page only if you need custom upload behavior or are building a custom UI.

</admonition>

## Uploading Files to a Channel

Files uploaded to a channel can be attached to messages. You can either upload a file first and then attach it to a message, or let the SDK handle the upload when sending a message with attachments.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// Upload an image to the channel
const response = await channel.sendImage(file);
const imageUrl = response.file;

// Send a message with the uploaded image
await channel.sendMessage({
  text: "Check out this image",
  attachments: [
    {
      type: "image",
      asset_url: imageUrl,
      thumb_url: imageUrl,
    },
  ],
});
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val channelClient = client.channel("messaging", "general")

// Upload an image
channelClient.sendImage(imageFile).enqueue { result ->
  if (result is Result.Success) {
    val imageUrl = result.value.file
    val attachment = Attachment(
      type = "image",
      imageUrl = imageUrl,
    )
    val message = Message(attachments = mutableListOf(attachment))
    channelClient.sendMessage(message).enqueue { /* ... */ }
  }
}

// Upload a file with progress tracking
channelClient.sendFile(
  file,
  object : ProgressCallback {
    override fun onSuccess(url: String?) {
      // File uploaded successfully
    }
    override fun onError(error: Error) {
      // Handle error
    }
    override fun onProgress(bytesUploaded: Long, totalBytes: Long) {
      // Update progress UI
    }
  }
).enqueue()
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let channelId = ChannelId(type: .messaging, id: "general")
let channelController = chatClient.channelController(for: channelId)

// Option 1: Send message with local attachment (SDK handles upload)
let imageAttachment = try AnyAttachmentPayload(
  localFileURL: localImageUrl,
  attachmentType: .image
)
channelController.createNewMessage(
  text: "Hello",
  attachments: [imageAttachment]
)

// Option 2: Upload first, then attach to message
channelController.uploadAttachment(
  localFileURL: localFileUrl,
  type: .image,
  progress: { value in
    // Track upload progress (0.0 to 1.0)
  },
  completion: { result in
    switch result {
    case .success(let uploadedFile):
      let payload = ImageAttachmentPayload(
        title: nil,
        imageRemoteURL: uploadedFile.url
      )
      let attachment = AnyAttachmentPayload(payload: payload)
      channelController.createNewMessage(
        text: "Hello",
        attachments: [attachment]
      )
    case .failure(let error):
      // Handle upload error
    }
  }
)
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// Option 1: Send message with local attachments (SDK handles upload)
final message = Message(text: "Hello", attachments: [
  Attachment(
    type: "image",
    file: AttachmentFile(path: "imagePath/imageName.png"),
  ),
]);
await channel.sendMessage(message);

// Option 2: Upload first, then attach to message
await client.sendImage(
  image,
  channelId,
  channelType,
  onSendProgress: (sent, total) {
    // Update progress UI
  },
).then((response) {
  final imageUrl = response.file;
  final message = Message(attachments: [
    Attachment(type: "image", imageUrl: imageUrl),
  ]);
  client.sendMessage(message, channelId, channelType);
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
ChannelClient channelClient = client.channel("messaging", "general");

// Upload an image
channelClient.sendImage(imageFile).enqueue(result -> {
  if (result.isSuccess()) {
    String imageUrl = result.data().getFile();
    Attachment attachment = new Attachment();
    attachment.setType("image");
    attachment.setImageUrl(imageUrl);

    Message message = new Message();
    message.getAttachments().add(attachment);
    channelClient.sendMessage(message).enqueue(res -> { /* ... */ });
  }
});

// Upload a file with progress tracking
channelClient.sendFile(file, new ProgressCallback() {
  @Override
  public void onSuccess(@NotNull String url) {
    // File uploaded successfully
  }
  @Override
  public void onError(@NotNull ChatError error) {
    // Handle error
  }
  @Override
  public void onProgress(long bytesUploaded, long totalBytes) {
    // Update progress UI
  }
}).enqueue();
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node.js">

```js
// Upload multiple images in parallel
const promises = [
  channel.sendImage(
    fs.createReadStream("./image1.jpg"),
    "image1.jpg",
    "image/jpeg",
    { id: "user-id" },
  ),
  channel.sendImage(
    fs.createReadStream("./image2.jpg"),
    "image2.jpg",
    "image/jpeg",
    { id: "user-id" },
  ),
];

const results = await Promise.all(promises);

const attachments = results.map((response) => ({
  type: "image",
  thumb_url: response.file,
  asset_url: response.file,
}));

await channel.sendMessage({
  text: "Check out these images",
  attachments,
});
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import OnlyUserID

channel.upload_channel_image(
    file="http://example.com/image.jpg",
    user=OnlyUserID(id="user-id"),
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
// Upload an image to the channel
// Note: actual file upload uses multipart form data
$response = $client->uploadChannelImage("messaging", "general", new Models\UploadChannelRequest(
    // file upload parameters
));
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.upload_channel_image('messaging', channel_id, Models::ImageUploadRequest.new(
  file: '/path/to/image.jpg'
))
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Upload image
var imgResp = await chat.UploadChannelImageAsync("messaging", channelId,
    new UploadChannelRequest
    {
        File = "/path/to/image.png",
        User = new OnlyUserID { ID = userId }
    });

// Upload file
var fileResp = await chat.UploadChannelFileAsync("messaging", channelId,
    new UploadChannelFileRequest
    {
        File = "/path/to/file.pdf",
        User = new OnlyUserID { ID = userId }
    });
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var imageBytes = File.ReadAllBytes("path/to/image.jpg");
var imageUploadResponse = await channel.UploadImageAsync(imageBytes, "image.jpg");
var imageUrl = imageUploadResponse.FileUrl;

var fileBytes = File.ReadAllBytes("path/to/document.pdf");
var fileUploadResponse = await channel.UploadFileAsync(fileBytes, "document.pdf");
var fileUrl = fileUploadResponse.FileUrl;
```

</codetabs-item>

</codetabs>

## Uploading Standalone Files

Files not tied to a specific channel can be used for user avatars, channel images, or other application needs.

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val client = ChatClient.instance()

// Upload an image
val uploadResult = client.uploadImage(
  file = imageFile,
  progressCallback = progressCallback,
).await()

when (uploadResult) {
  is Result.Success -> {
    val imageUrl = uploadResult.value.file
    // Use the URL (e.g., update user avatar)
    client.updateUser(user.copy(image = imageUrl)).await()
  }
  is Result.Failure -> {
    // Handle error
  }
}
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let chatClient = ChatClient.shared

chatClient.uploadAttachment(
  localUrl: imageLocalFileUrl,
  progress: { progressValue in
    // Track upload progress (0.0 to 1.0)
  },
  completion: { result in
    switch result {
    case .success(let uploadedFile):
      // Use the URL (e.g., update user avatar)
      chatClient.currentUserController().updateUserData(
        imageURL: uploadedFile.url
      )
    case .failure(let error):
      // Handle upload error
    }
  }
)
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final client = StreamChatClient("api-key");

// Upload an image
final image = AttachmentFile(path: "imagePath/image.png");
final response = await client.uploadImage(
  image,
  onUploadProgress: (count, total) {
    // Update progress UI
  },
);

final imageUrl = response.file;
// Use the URL (e.g., update user avatar)
final user = User(id: "user-id", image: imageUrl);
await client.updateUser(user);
```

</codetabs-item>

</codetabs>

## Deleting Files

Delete uploaded files to free storage space. Deleting a file from the CDN does not remove it from message attachments that reference it.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// Delete from channel
await channel.deleteFile(fileURL);
await channel.deleteImage(imageURL);
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// Delete from channel
channelClient.deleteFile("file-url").enqueue()
channelClient.deleteImage("image-url").enqueue()

// Delete standalone file
client.deleteFile("file-url").enqueue()
client.deleteImage("image-url").enqueue()
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
// Delete from channel
channelController.deleteFile(url: "remote-url")
channelController.deleteImage(url: "remote-url")

// Delete standalone file
ChatClient.shared.deleteAttachment(remoteUrl: "remote-url") { error in
  // Handle deletion error
}
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// Delete from channel
await channel.deleteFile("file-url");
await channel.deleteImage("image-url");

// Delete standalone file
await client.removeFile("file-url");
await client.removeImage("image-url");
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
ChannelClient channelClient = client.channel("messaging", "general");
channelClient.deleteImage("image-url").enqueue();
channelClient.deleteFile("file-url").enqueue();

// Backend SDK
chat.deleteChannelImage(channelType, channelId, DeleteChannelImageRequest.builder()
    .Url(url)
    .build()).execute();
chat.deleteChannelFile(channelType, channelId, DeleteChannelFileRequest.builder()
    .Url(url)
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node.js">

```js
await channel.deleteFile(fileURL);
await channel.deleteImage(imageURL);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
channel.delete_channel_file(url=url)
channel.delete_channel_image(url=url)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
$client->deleteChannelFile("messaging", "general", $url);
$client->deleteChannelImage("messaging", "general", $url);
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
require 'getstream_ruby'
Models = GetStream::Generated::Models

client.chat.delete_channel_file('messaging', channel_id, url)
client.chat.delete_channel_image('messaging', channel_id, url)
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
channel.DeleteChannelFile(ctx, &getstream.DeleteChannelFileRequest{
    Url: getstream.PtrTo(url),
})
channel.DeleteChannelImage(ctx, &getstream.DeleteChannelImageRequest{
    Url: getstream.PtrTo(url),
})
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// Delete file
await chat.DeleteChannelFileAsync("messaging", channelId, new { url = fileUrl });

// Delete image
await chat.DeleteChannelImageAsync("messaging", channelId, new { url = imageUrl });
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
await channel.DeleteFileOrImageAsync("file-url");
```

</codetabs-item>

</codetabs>

## File Requirements

### Images

| Requirement       | Value                                                                        |
| ----------------- | ---------------------------------------------------------------------------- |
| Supported formats | BMP, GIF, JPEG, PNG, WebP, HEIC, HEIC-sequence, HEIF, HEIF-sequence, SVG+XML |
| Maximum file size | 100 MB                                                                       |

### Other Files

| Requirement       | Value                                                                                          |
| ----------------- | ---------------------------------------------------------------------------------------------- |
| Supported formats | All file types are allowed by default. Different clients may handle certain types differently. |
| Maximum file size | 100 MB                                                                                         |

You can configure a more restrictive list of allowed file types for your application.

## Configuring Allowed File Types

Stream allows all file extensions by default. To restrict allowed file types:

- **Dashboard**: Go to Chat Overview > Upload Configuration
- **API**: Use the [App Settings](/chat/docs/<framework>/app_setting_overview/#file-uploads/) endpoint

## Access Control and Link Expiration

Stream CDN URLs include a signature that validates access to the file. Only channel members can access files uploaded to that channel.

| Behavior          | Description                                                                                   |
| ----------------- | --------------------------------------------------------------------------------------------- |
| Access control    | URLs are signed and only accessible by channel members                                        |
| Link expiration   | URLs expire after 14 days                                                                     |
| Automatic refresh | Links are refreshed automatically when messages are retrieved (e.g., when querying a channel) |
| Manual refresh    | Call `getMessage` to retrieve fresh URLs for expired attachments                              |

To check when a link expires, examine the `Expires` query parameter in the URL (Unix timestamp).

## Image Resizing

Append query parameters to Stream CDN image URLs to resize images on the fly.

| Parameter | Type   | Values                           | Description          |
| --------- | ------ | -------------------------------- | -------------------- |
| w         | number |                                  | Width in pixels      |
| h         | number |                                  | Height in pixels     |
| resize    | string | clip, crop, scale, fill          | Resizing mode        |
| crop      | string | center, top, bottom, left, right | Crop anchor position |

<admonition type="warning">

Images can only be resized if the source image has 16,800,000 pixels or fewer. An image of 4000x4000 pixels (16,000,000) would be accepted, but 4100x4100 (16,810,000) would fail.

</admonition>

<admonition type="info">

Resized images count against your storage quota.

</admonition>

## Using Your Own CDN

All SDKs support custom CDN implementations. Implement a custom file uploader to use your own storage solution.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
messageComposer.attachmentManager.setCustomUploadFn(async (file) => {
  const result = await customCDN.upload(file);
  return { file: result.url };
});
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val client = ChatClient.Builder("api-key", context)
  .fileUploader(MyCustomFileUploader())
  .build()
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
class CustomCDN: CDNClient {
  static var maxAttachmentSize: Int64 { 20 * 1024 * 1024 }

  func uploadAttachment(
    _ attachment: AnyChatMessageAttachment,
    progress: ((Double) -> Void)?,
    completion: @escaping (Result<URL, Error>) -> Void
  ) {
    // Upload to your CDN
    // Call progress() to report upload progress
    // Call completion() with the result
  }
}

// Assign to ChatClientConfig
config.customCDNClient = CustomCDN()
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final client = StreamChatClient(
  "api-key",
  attachmentFileUploader: MyCustomFileUploader(),
);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// Android SDK
ChatClient client = new ChatClient.Builder("api-key", context)
    .fileUploader(new MyCustomFileUploader())
    .build();

// Backend SDK
chat.uploadChannelFile(channelType, channelId, UploadChannelFileRequest.builder()
    // file data
    .build()).execute();
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Upload to your CDN and use the returned URL
var fileUrl = await MyCustomCDN.Upload(fileBytes);

await channel.SendNewMessageAsync(new StreamSendMessageRequest
{
  Text = "Message with file attachment",
  Attachments = new List<StreamAttachmentRequest>
  {
    new StreamAttachmentRequest
    {
      AssetUrl = fileUrl,
    }
  }
});
```

</codetabs-item>

</codetabs>


---

This page was last updated at 2026-03-13T13:15:47.321Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/android/file_uploads/](https://getstream.io/chat/docs/android/file_uploads/).