# File Uploads

Stream allows you to easily upload files to our CDN and use them as attachments for activities.

<admonition type="info">

This functionality defaults to using the Stream CDN. If you would like, you can easily change the logic to upload to your own CDN of choice.

</admonition>

## How to upload a file or image

<codetabs>

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

```swift
// In order to upload a file or image, you can use the `StreamAttachmentUploader`.
// The StreamAttachmentUploader is a thread-safe, asynchronous uploader for `AnyStreamAttachment` objects.
// It wraps a lower-level CDNClient implementation to simplify file uploads and provide progress updates and completion callbacks.
import StreamCore
import StreamFeeds

func uploadAttachmentPayloads(_ attachments: [AnyAttachmentPayload], in fid: FeedId) async throws -> [Attachment] {
    let dataAttachments: [StreamAttachment<Data>] = try attachments
        .filter { $0.localFileURL != nil }
        .enumerated()
        .compactMap { index, attachment in
            guard let localFileURL = attachment.localFileURL else { return nil }
            let attachmentFile = try AttachmentFile(url: localFileURL)
            let payloadData = try JSONEncoder().encode(attachment.payload)
            try Task.checkCancellation()
            return StreamAttachment<Data>(
                id: AttachmentId(
                    fid: fid.rawValue,
                    activityId: UUID().uuidString,
                    index: index
                ),
                type: attachment.type,
                payload: payloadData,
                downloadingState: nil,
                uploadingState: .init(
                    localFileURL: localFileURL,
                    state: .pendingUpload,
                    file: attachmentFile
                )
            )
    }
    return try await attachmentUploader.upload(dataAttachments, progress: nil)
        .map { uploadedAttachment in
            Attachment(
                assetUrl: uploadedAttachment.remoteURL.absoluteString,
                custom: [:],
                imageUrl: uploadedAttachment.remoteURL.absoluteString
            )
        }
}
```

You can then use these uploads as attachments when creating activity:

```swift
let activity = try await feed.addActivity(
    request: .init(
        attachments: uploadedAttachments,
        text: "Hi",
        type: "activity"
    )
)
```

Alternatively, you can directly pass local attachments and they will be automatically uploaded:

```swift
let payloads: [AnyAttachmentPayload] = [] // set payloads
let activity = try await feed.addActivity(
    request: .init(
        attachmentUploads: payloads,
        text: "Hi",
        type: "activity"
    )
)
```

</codetabs-item>

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

```ts
import { isImageFile } from "@stream-io/feeds-client";
import type { StreamFile } from "@stream-io/feeds-client";

const files: null | StreamFile[]; // the return value of a file input

const requests = [];
for (let file of [...(files ?? [])]) {
  if (isImageFile(file)) {
    requests.push(
      client.uploadImage({
        file,
        // Optionally provide resize params
        upload_sizes: [
          {
            width: 100,
            height: 100,
            resize: "scale",
            crop: "center",
          },
        ],
      }),
    );
  } else {
    requests.push(
      client.uploadFile({
        file,
      }),
    );
  }
}
const fileResponses = await Promise.all(requests);

await feed.addActivity({
  type: "post",
  text: activityText,
  attachments: fileResponses.map((response, index) => {
    const isImage = isImageFile(files![index]);
    return {
      type: isImage ? "image" : "file",
      [isImage ? "image_url" : "asset_url"]: response?.file,
      custom: {},
    };
  }),
});
```

</codetabs-item>

<codetabs-item value="react" label="React">

```tsx
import { isImageFile } from "@stream-io/feeds-react-sdk";
import type { StreamFile } from "@stream-io/feeds-react-sdk";

const files: null | StreamFile[]; // the return value of a file input

const requests = [];
for (let file of [...(files ?? [])]) {
  if (isImageFile(file)) {
    requests.push(
      client.uploadImage({
        file,
        // Optionally provide resize params
        upload_sizes: [
          {
            width: 100,
            height: 100,
            resize: "scale",
            crop: "center",
          },
        ],
      }),
    );
  } else {
    requests.push(
      client.uploadFile({
        file,
      }),
    );
  }
}
const fileResponses = await Promise.all(requests);

await feed.addActivity({
  type: "post",
  text: activityText,
  attachments: fileResponses.map((response, index) => {
    const isImage = isImageFile(files![index]);
    return {
      type: isImage ? "image" : "file",
      [isImage ? "image_url" : "asset_url"]: response?.file,
      custom: {},
    };
  }),
});
```

</codetabs-item>

<codetabs-item value="reactnative" label="React Native">

```tsx
import { isImageFile } from "@stream-io/feeds-react-native-sdk";
import type { StreamFile } from "@stream-io/feeds-react-native-sdk";

const files: null | StreamFile[]; // the return value of a file input

const requests = [];
for (let file of [...(files ?? [])]) {
  if (isImageFile(file)) {
    requests.push(
      client.uploadImage({
        file,
        // Optionally provide resize params
        upload_sizes: [
          {
            width: 100,
            height: 100,
            resize: "scale",
            crop: "center",
          },
        ],
      }),
    );
  } else {
    requests.push(
      client.uploadFile({
        file,
      }),
    );
  }
}
const fileResponses = await Promise.all(requests);

await feed.addActivity({
  type: "post",
  text: activityText,
  attachments: fileResponses.map((response, index) => {
    const isImage = isImageFile(files![index]);
    return {
      type: isImage ? "image" : "file",
      [isImage ? "image_url" : "asset_url"]: response?.file,
      custom: {},
    };
  }),
});
```

</codetabs-item>

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

```js
import fs from "fs";
import path from "path";
import { File } from "buffer";

const filePath = path.join(__dirname, "assets", "test-image.jpg");
const fileBuffer = fs.readFileSync(filePath);

const response = await client.uploadImage({
  file: new File([fileBuffer], "test-image.jpg"),
  user: { id: user.id },
  upload_sizes: [
    {
      width: 100,
      height: 100,
      resize: "scale",
      crop: "center",
    },
  ],
});

const filePath2 = path.join(__dirname, "assets", "test-file.pdf");
const fileBuffer2 = fs.readFileSync(filePath2);

const response = await client.uploadFile({
  file: new File([fileBuffer2], "test-file.pdf"),
  user: { id: user.id },
});
```

</codetabs-item>

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

```go
import (
    "context"
    "encoding/base64"
    "io/ioutil"
    "log"

    "github.com/GetStream/getstream-go/v3"
)

// Upload file
func uploadFile() {
    client, err := getstream.NewClient("<your_api_key>", "<your_api_secret>")
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    // Read file from disk
    fileContent, err := ioutil.ReadFile("path/to/your/file.pdf")
    if err != nil {
        log.Fatal(err)
    }

    // Encode file content to base64
    encodedFile := base64.StdEncoding.EncodeToString(fileContent)

    // Upload file
    uploadResponse, err := client.UploadFile(ctx, &getstream.UploadFileRequest{
        File: &encodedFile,
        User: &getstream.OnlyUserID{
            ID: "user-id",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("File uploaded successfully: %s", uploadResponse.Data.File)
}

// Upload image with specific sizes
func uploadImage() {
    client, err := getstream.NewClient("<your_api_key>", "<your_api_secret>")
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    // Read image file
    imageContent, err := ioutil.ReadFile("path/to/your/image.jpg")
    if err != nil {
        log.Fatal(err)
    }

    // Encode image content to base64
    encodedImage := base64.StdEncoding.EncodeToString(imageContent)

    // Upload image with different sizes
    uploadResponse, err := client.UploadImage(ctx, &getstream.UploadImageRequest{
        File: &encodedImage,
        UploadSizes: []getstream.ImageSize{
            {
                Crop:   getstream.PtrTo("center"),
                Resize: getstream.PtrTo("fill"),
                Width:  getstream.PtrTo(300),
                Height: getstream.PtrTo(300),
            },
            {
                Crop:   getstream.PtrTo("center"),
                Resize: getstream.PtrTo("fill"),
                Width:  getstream.PtrTo(100),
                Height: getstream.PtrTo(100),
            },
        },
        User: &getstream.OnlyUserID{
            ID: "user-id",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("Image uploaded successfully: %s", uploadResponse.Data.File)
}
```

</codetabs-item>

</codetabs>

### Deleting Files and Images

We expose two methods for deleting files and images, `client.deleteImage` and `client.deleteFile`

<codetabs>

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

```swift
try await client.deleteImage(url: "remote-image-url")
try await client.deleteFile(url: "remote-file-url")
```

</codetabs-item>

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

```js
await client.deleteImage({
  url: "<image url>",
});

await client.deleteFile({
  url: "<file url>",
});
```

</codetabs-item>

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

```js
await client.deleteImage({
  url: "<image url>",
});

await client.deleteFile({
  url: "<file url>",
});
```

</codetabs-item>

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

```go
package main

import (
    "context"
    "log"

    "github.com/GetStream/getstream-go/v3"
)

func main() {
    client, err := getstream.NewClient("<your_api_key>", "<your_api_secret>")
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    // Delete an image using the URL returned by the upload API
    deleteImageResponse, err := client.DeleteImage(ctx, &getstream.DeleteImageRequest{
        URL: "<image url>",
    })
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Image deleted successfully: %+v", deleteImageResponse)

    // Delete a file using the URL returned by the upload API
    deleteFileResponse, err := client.DeleteFile(ctx, &getstream.DeleteFileRequest{
        URL: "<file url>",
    })
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("File deleted successfully: %+v", deleteFileResponse)
}
```

</codetabs-item>

</codetabs>

### Requirements for Images

- Stream supported image types are: image/bmp, image/gif, image/jpeg, image/png, image/webp, image/heic, image/heic-sequence, image/heif, image/heif-sequence, image/svg+xml.

- You can set a more restrictive list for your application if needed.

- The maximum file size is 100MB.

### Requirements for Files

- Stream will not block any file types from uploading, however, different clients may handle different types differently or not at all.

- You can set a more restrictive list for your application if needed.

- The maximum file size is 100MB.

## How to Allow/Block file extensions

Stream will allow any file extension. If you want to be more restrictive for an application, this is can be set via API or by logging into your dashboard.

- To update via the dashboard, login and go to the Chat Overview page >> Upload Configuration.

- API updates are made using UpdateAppSettings.

## Image resizing

You can automatically resize an image appending query parameters to a valid image link stored on the Stream CDN.

<admonition type="info">

An image can only be resized if the total pixel count of the source image is 16.800.000 or less. Attempting to resize an image with more pixels will result in an API error. An image of 4000 by 4000 would be accepted, but an image of 4100 by 4100 would pass the upper treshold for resizing.

</admonition>

There are four supported params - all of them are optional and can be used interchangeably:

| Parameter | Type   | Values                               | Description                          |
| --------- | ------ | ------------------------------------ | ------------------------------------ |
| w         | number |                                      | Width                                |
| h         | number |                                      | Height                               |
| resize    | string | clip , crop , scale , fill           | The resizing mode                    |
| crop      | string | center , top , bottom , left , right | The cropping direction during resize |

<admonition type="info">

Resized images will count against your stored files quota.

</admonition>

## Access control and link expiration

The Stream CDN URL returned during the upload contains a signature that validates the access to the file it points to. Only the members of a channel a file was uploaded to can see the attachment and its unique, signed link. Links can only be accessed with a valid signature, which also protects against enumeration attacks.

Whenever messages containing your attachments are retrieved (i.e., when querying a channel), the attachment links will contain a new, fresh signature.

A single Stream CDN URL expires after 14 days, after which its signature will stop working and the link won't be valid anymore. You can check when a link will expire by comparing the current time with the Unix timestamp in the Expires parameter of the link's query: _<https://us-east.stream-io-cdn.com/0000/images/foo.png?...&>_ **Expires=1602666347** _&..._

## Using Your Own CDN

All our SDKs make it easy to use your own CDN for uploads. The code examples below show how to change where files are uploaded:

<codetabs>

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

```swift
// Your custom implementation of `CDNClient`.
class CustomCDN: CDNClient, @unchecked Sendable {
    static var maxAttachmentSize: Int64 { 20 * 1024 * 1024 }

    func uploadAttachment(
        _ attachment: AnyStreamAttachment,
        progress: (@Sendable (Double) -> Void)?,
        completion: @Sendable @escaping (Result<URL, Error>) -> Void
    ) {
        if let imageAttachment = attachment.attachment(payloadType: ImageAttachmentPayload.self) {
            // Your code to handle image uploading.
            // Don't forget to call `progress(x)` to report back the uploading progress.
            // When the uploading is finished, call the completion block with the result.
            uploadImage(imageAttachment, progress: progress)
        } else if let fileAttachment = attachment.attachment(payloadType: FileAttachmentPayload.self) {
            // Your code to handle file uploading.
            // Don't forget to call `progress(x)` to report back the uploading progress.
            // When the uploading is finished, call the completion block with the result.
            uploadFile(fileAttachment, progress: progress)
        } else {
            // Unsupported attachment type
            struct UnsupportedAttachmentType: Error {}
            completion(.failure(UnsupportedAttachmentType()))
        }
    }
}

// Assign your custom CDN client to the `FeedsConfig` instance you use
// when creating `FeedsClient`.
let config = FeedsConfig(customCDNClient: CustomCDN())
let client = FeedsClient(
    apiKey: APIKey("api key"),
    user: User(id: "user_id"),
    token: UserToken("my token"),
    feedsConfig: config
)
```

</codetabs-item>

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

```js
// No need to set anything, just call your own CDN instead of client.uploadFile / client.uploadImage
```

</codetabs-item>

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

```js
// No need to set anything, just call your own CDN instead of client.uploadFile / client.uploadImage
```

</codetabs-item>

</codetabs>

---

This page was last updated at 2025-08-15T14:14:10.085Z.

For the most recent version of this documentation, visit [https://getstream.io/activity-feeds/docs/react-native/file_uploads/](https://getstream.io/activity-feeds/docs/react-native/file_uploads/).