val channelClient = client.channel("messaging", "general")
// Upload an image without detailed progress
channelClient.sendImage(imageFile).enqueue { result ->
if (result is Result.Success) {
// Successful upload, you can now attach this image
// to a message that you then send to a channel
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, monitoring for progress with a ProgressCallback
channelClient.sendFile(
anyOtherFile,
object : ProgressCallback {
override fun onSuccess(url: String?) {
val fileUrl = url
}
override fun onError(error: Error) {
// Handle error
}
override fun onProgress(bytesUploaded: Long, totalBytes: Long) {
// You can render the uploading progress here.
}
}
).enqueue() // No callback passed to enqueue, as we'll get notified above anywayFile Uploads
Stream Chat provides methods to upload files like images, videos and more to the Stream CDN or to your own CDN. This allows you to use them, for example, as attachments in messages, user avatars or channel avatars.
Uploading files
When uploading a file, it can belong to a channel so that it can be used as an attachment in a message, or it can be a standalone file to be used anywhere in your application, like a user profile image or channel avatar.
Attachments
For uploading files as message attachments in a channel, you can either send the attachment directly as part of the message and let the SDK handle the upload progress, or you can upload the file to the channel first, and then attach it to the message.
// Capture a file using the HTML5 input element
// or a similar feature in your preferred web framework.
//
// Example: <input type="file" onChange={getUrl}></input>
async function getUrl(event) {
const files = event.target.files;
const response = await channel.sendImage(files[0]);
}import StreamChat
// Create the channel controller
let channelId = ChannelId(type: .messaging, id: "general")
let channelController = ChatClient.shared.channelController(for: channelId)
// Create a message with a local image, this will create the message
// locally and handle the attachment upload automatically.
let imageAttachment = try AnyAttachmentPayload(
localFileURL: localImageUrl,
attachmentType: .image
)
channelController.createNewMessage(
text: "Hello",
attachments: [imageAttachment]
)
// Upload an image to the channel first, and then attach it to the 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 remoteUrl = uploadedFile.url
let payload = ImageAttachmentPayload(
title: nil,
imageRemoteURL: remoteUrl
)
let attachment = AnyAttachmentPayload(payload: payload)
channelController.createNewMessage(
text: "Hello",
attachments: [attachment]
)
case .failure(let error):
// Handle upload error
}
}
)$user = [ 'id' => 'jenny' ];
$response = $channel->sendImage('http://bit.ly/2O35mws', 'image.jpeg', $user);final client = StreamChatClient('api-key');
var attachment = Attachment();
// Upload an image without monitoring send progress
client.sendImage(image, channelId, channelType).then((response) {
// Successful upload, you can now attach this image
// to an message that you then send to a channel
final imageUrl = response.file;
attachment = attachment.copyWith(
type: 'image',
imageUrl: imageUrl,
);
final message = Message(attachments: [attachment]);
client.sendMessage(message, channelId, channelType);
}).catchError((error, stk) {
// Handle error
});
// Upload an file, monitoring the progress with a onSendProgress callback
await client.sendFile(
file,
channelId,
channelType,
onSendProgress: (sent, total) {
// Handle the send progress
attachment = attachment.copyWith(
uploadState: UploadState.inProgress(
uploaded: sent,
total: total,
),
);
},
).then((response) {
// Successful upload, you can now attach this file
// to an message that you then send to a channel
final fileUrl = response.file;
attachment = attachment.copyWith(
type: 'file',
assetUrl: fileUrl,
uploadState: UploadState.success(),
);
final message = Message(attachments: [attachment]);
client.sendMessage(message, channelId, channelType);
}).catchError((error, stk) {
// Handle error
attachment = attachment.copyWith(
uploadState: UploadState.failed(error: error),
);
});
// Alternatively you can call the sendMessage directly on the channel
// which will automatically handle all the upload process of the provided
// attachments.
final channel = client.channel(channelType, id: channelId);
// Creating a message object with multiple local attachments
final message = Message(text: 'Hello', attachments: [
Attachment(
type: 'image',
file: AttachmentFile(path: 'imagePath/imageName.png'),
),
Attachment(
type: 'file',
file: AttachmentFile(path: 'filePath/fileName.pdf'),
),
]);
// Sending the message to the channel
await channel.sendMessage(message);const promises = [
channel.sendImage(
fs.createReadStream("./helloworld.jpg"),
"hello_world1.jpg",
"hello_world1/jpg",
{ id: "sara" }, // inform user id if server-side auth
),
channel.sendImage(
fs.createReadStream("./helloworld.jpg"),
"hello_world2.jpg",
"hello_world2/jpg",
{ id: "sara" }, // inform user id if server-side auth
),
];
const results = await Promise.all(promises);
const attachments = results.map((response) => {
return {
type: "image",
thumb_url: response.file,
asset_url: response.file,
};
});
const response = await channel.sendMessage({
text: "Check out what I have uploaded in parallel",
attachments,
});
expect(response.message.attachments).to.equal(attachments);// Not yet supported in the Unreal SDKchannel.send_image(
"[http://bit.ly/2O35mws](http://bit.ly/2O35mws)", "helloworld.jpg", {"id": "jenny"}, "image/jpeg",
)# require 'stream-chat'
channel.send_image(
"[http://bit.ly/2O35mws](http://bit.ly/2O35mws)", "helloworld.jpg", {id: "jenny"}, "image/jpeg",
)var jpegFile = File.ReadAllBytes("helloworld.jpg");
await messageClient.UploadImageAsync(channel.Type, channel.Id, user, jpegFile, "helloworld.jpg");// Get file byte array however you want e.g. Addressables.LoadAsset, Resources.Load, etc.
var sampleFile = File.ReadAllBytes("path/to/file");
var fileUploadResponse = await channel.UploadFileAsync(sampleFile, "my-file-name");
var fileWebUrl = fileUploadResponse.FileUrl;
// Get image byte array however you want e.g. Addressables.LoadAsset, Resources.Load, etc.
var sampleImage = File.ReadAllBytes("path/to/file");
var imageUploadResponse = await channel.UploadImageAsync(sampleFile, "my-image-name");
var imageWebUrl = imageUploadResponse.FileUrl;// Android SDK
ChannelClient channelClient = client.channel("messaging", "general");
// Upload an image without detailed progress
channelClient.sendImage(imageFile).enqueue(result -> {
if (result.isSuccess()) {
// Successful upload, you can now attach this image
// to a message that you then send to a channel
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, monitoring for progress with a ProgressCallback
channelClient.sendFile(anyOtherFile, new ProgressCallback() {
@Override
public void onSuccess(@NotNull String file) {
String fileUrl = file;
}
@Override
public void onError(@NotNull ChatError error) {
// Handle error
}
@Override
public void onProgress(long bytesUploaded, long totalBytes) {
// You can render the uploading progress here
}
}).enqueue(); // No callback passed to enqueue, as we'll get notified above anyway
// Backend SDK
String pngFileUrl =
Message.uploadImage("messaging", "general", userId, "image/png")
.file(new File("./helloworld.png"))
.request()
.getFile();
Message.send("messaging", "general")
.message(
MessageRequestObject.builder()
.text("Check out what I have uploaded in parallel")
.attachment(
AttachmentRequestObject.builder()
.type("image")
.assetURL(pngFileUrl)
.thumbURL(pngFileUrl)
.build())
.userId(userId)
.build())
.request();Standalone
In some cases, you may need to upload files that are not tied to a specific channel or message. For example, uploading a user profile image or channel avatar. These uploads are independent and can be used anywhere in your application.
import StreamChat
// Upload an image to the CDN.
let chatClient = ChatClient.shared
let imageLocalFileUrl = try image.tempLocalFileUrl()
chatClient.uploadAttachment(
localUrl: imageLocalFileUrl,
progress: { progressValue in
// Track upload progress (0.0 to 1.0)
},
completion: { result in
switch result {
case .success(let uploadedFile):
let remoteUrl = uploadedFile.url
// Example: Update user avatar
chatClient.currentUserController().updateUserData(
imageURL: remoteUrl
)
case .failure(let error):
// Handle upload error
}
}
)
/// Example on how to save an image to a temporary file URL.
extension UIImage {
func tempLocalFileUrl() throws -> URL? {
guard let imageData = jpegData(compressionQuality: 1.0) else { return nil }
let imageName = "\(UUID().uuidString).jpg"
let localPath = NSTemporaryDirectory().appending(imageName)
let photoURL = URL(fileURLWithPath: localPath)
try imageData.write(to: photoURL)
return photoURL
}
}final client = StreamChatClient('api-key');
// Upload an image to the CDN
final image = AttachmentFile(path: 'imagePath/imageName.png');
final response = await client.uploadImage(
image,
onUploadProgress: (count, total) {
// Handle the upload progress
},
).then((response) {
final imageUrl = response.file;
// Example: Update user avatar
final user = User(id: 'test-user-id', image: imageUrl);
return client.updateUser(user);
}).catchError((e, stk) {
// Handle upload error
});
// or Upload a file to the CDN
final file = AttachmentFile(path: 'filePath/fileName.pdf');
final response = await client.uploadFile(
file,
onUploadProgress: (count, total) {
// Handle the upload progress
},
).then((response) {
final fileUrl = response.file;
// Example: Update user profile with file URL
final user = User(id: 'test-user-id', extraData: {'fileUrl': fileUrl});
return client.updateUser(user);
}).catchError((e, stk) {
// Handle upload error
});Deleting files
If you want to save storage space you can delete the uploaded files.
val channelClient = client.channel("messaging", "general")
// Deletes the image
channelClient.deleteImage("{{ url of uploaded image }}").enqueue()
// Deletes the file
channelClient.deleteFile("{{ url of uploaded file }}").enqueue()// Delete File
await channel.deleteFile(fileURL);
// Delete Image
await channel.deleteImage(imageURL);// Not yet supported in the Unreal SDKChannelClient channelClient = client.channel("messaging", "general");
// Deletes the image
channelClient.deleteImage("{{ url of uploaded image }}").enqueue();
// Deletes the file
channelClient.deleteFile("{{ url of uploaded file }}").enqueue();// Android SDK
ChannelClient channelClient = client.channel("messaging", "general");
// Deletes the image
channelClient.deleteImage("{{ url of uploaded image }}").enqueue();
// Deletes the file
channelClient.deleteFile("{{ url of uploaded file }}").enqueue();
// Backend SDK
Message.deleteImage(channelType, channelId, url).request();
Message.deleteFile(channelType, channelId, url).request();// Delete file
$channel->deleteFile($url);
// Delete image
$channel->deleteImage($url);# Delete a file
channel.delete_file(url)
# Delete an image
channel.delete_image(url)# Delete a file
channel.delete_file(url)
# Delete an image
channel.delete_image(url)// Delete File
channel.DeleteFile(ctx, url)
// Delete Image
channel.DeleteImage(ctx, url)// Delete File
await messageClient.DeleteFileAsync("<channel-type>", "<channel-id>", url);
// Delete Image
await messageClient.DeleteImageAsync("<channel-type>", "<channel-id>", url);await channel.DeleteFileOrImageAsync("file-url");// Delete a file from the channel.
channelController.deleteFile(url: "remote-url")
// Delete an image from the channel.
channelController.deleteImage(url: "remote-url")
// Delete a standalone file.
ChatClient.shared.deleteAttachment(remoteUrl: "remote-url") { error in
// Handle deletion error.
}// Delete a file from the channel.
await channel.deleteFile("file-url");
// Delete an image from the channel.
await channel.deleteImage("image-url");
// Delete a standalone file.
await client.removeFile("file-url");
// Delete a standalone image.
await client.removeImage("image-url");File Requirements
Stream has specific requirements for the files that can be uploaded to the CDN.
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.
Other 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.
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, see the App Settings page
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&…
As explained above, all operations that return messages will refresh links automatically, but if you are storing a message containing an expired link you can perform a getMessage call to retrieve fresh links for its attachments.
Image resizing
You can automatically resize an image appending query parameters to a valid image link stored on the Stream CDN.
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.
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 |
Resized images will count against your stored files quota.
Using your own CDN
All SDKs make it easy to use your own CDN for file uploads. The code examples below show how to use a custom CDN client.
// Set a custom FileUploader implementation when building your client
val client = ChatClient.Builder("{{ api_key }}", context)
.fileUploader(MyFileUploader())
.build()
}messageComposer.attachmentManager.setCustomUploadFn(async (file) => {
// Upload to custom CDN
const result = await customCDN.upload(file);
return { file: result.url };
});// Set a custom FileUploader implementation when building your client
final client = StreamChatClient(
'api-key',
attachmentFileUploader: MyFileUploader(),
);/// Your custom implementation of `CDNClient`.
class CustomCDN: CDNClient {
static var maxAttachmentSize: Int64 { 20 * 1024 * 1024 }
func uploadAttachment(
_ attachment: AnyChatMessageAttachment,
progress: ((Double) -> Void)?,
completion: @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.
} 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.
} else {
// Unsupported attachment type
struct UnsupportedAttachmentType: Error {}
completion(.failure(UnsupportedAttachmentType()))
}
}
}
// Assign your custom CDN client to the `ChatClientConfig` instance you use
// for creating `ChatClient`.
config.customCDNClient = CustomCDN()// Not yet supported in the Unreal SDK// Android SDK
// Set a custom FileUploader implementation when building your client
ChatClient client = new ChatClient.Builder("{{ api_key }}", context)
.fileUploader(new MyFileUploader())
.build();
// Backend SDK
Message.uploadFile(
testChannel.getType(),
testChannel.getId(),
testUserRequestObject.getId(),
"text/plain")
.file(new File(testFileUrl.getFile()))
.withFileHandler(fileHandler) // Set custom implementation here
.request();// Implement your own CDN upload and obtain the file URL
var fileUrl = "file-url-to-your-cdn";
await channel.SendNewMessageAsync(new StreamSendMessageRequest
{
Text = "Message with file attachment",
Attachments = new List<StreamAttachmentRequest>
{
new StreamAttachmentRequest
{
AssetUrl = fileUrl,
}
}
});