CometChat.createUploadFileRequest(receiverId:receiverType:) returns an UploadFileRequest — the entry point for uploading files directly to storage with per-file progress, success, and failure. Upload is decoupled from sending: each uploaded file yields an Attachment (carrying a hosted URL), which you then attach to a MediaMessage and send with sendMediaMessage().
A request object is scoped to one destination (receiverId / receiverType) and one upload batch. This is the recommended way to build a multi-attachment composer: create a request, upload a batch of files, show a progress bar per file, let the user remove or retry individual files, then send them as a single media message with multiple attachments (or split across several).
Why upload separately instead of passing files to
sendMediaMessage()?The classic path (passing a File, or an array of files, straight to the MediaMessage constructor) uploads and sends in one blocking call — you get no progress, no per-file remove, and no per-file retry. An UploadFileRequest moves the upload out of the send call so you can drive a rich composer UI, then send instantly because the files are already hosted.The upload-then-send flow
1
Create a request
Call
CometChat.createUploadFileRequest(receiverId:receiverType:). The recipient is required — the server uses it to apply role- and scope-based access control before issuing upload URLs.2
Upload
Call
request.uploadAttachments(_:listener:), where each UploadFileItem pairs a file with an app-supplied fileId (required) that is echoed back on every event, so you can map callbacks to your UI rows. Validation, presigning, and the byte transfer run asynchronously and report through the listener.3
Track progress & handle failures
Your
UploadFileListener receives onFileProgress per file, then onFileUploaded (success), onFileError (rejected — not retryable), or onFileFailure (failed — retryable). Use request.removeAttachment(fileId:) or request.retryAttachment(fileId:) as the user acts.4
Collect attachments
Each
onFileUploaded hands you an Attachment with a hosted URL. You can also read them from the request at any time with request.getAttachments() / request.getAttachmentsByType(_:).5
Build & send the message
Put the attachments on a
MediaMessage via its attachments property and call sendMediaMessage(). Because the attachments already have URLs, the message is sent as JSON — no re-upload.6
Clean up
Call
request.clearAll() after a successful send (or to abandon the composer) to release the batch from memory.Create an upload request
createUploadFileRequest(receiverId:receiverType:) accepts:
Upload files
Build anUploadFileListener and call uploadAttachments(_:listener:) (or uploadAttachment(fileId:file:listener:) for a single file). Each UploadFileItem pairs a File (a name plus its Data) with a required, app-supplied fileId:
fileId is app-supplied and required. The SDK does not generate one — you provide a stable id per file (echoed back unchanged on every event) so you can line each file up with its UI row. The app owns id uniqueness within the batch: re-using the fileId of a failed file re-uploads it (that is one of the retry paths); re-using the id of a file in any other live state is rejected via onFileError with ERR_INVALID_FILE_OBJECT.Validation, presigning, and the byte transfer all run asynchronously after uploadAttachments returns — track outcomes through the listener.The UploadFileListener
UploadFileListener is a protocol whose methods are all optional — implement only the callbacks you need. Unlike CometChatMessageDelegate, it is not registered globally with a string id; it lives for the duration of the upload batch, and the request holds it strongly until its files leave the batch.
UploadResult
onComplete receives the batch’s settled state. It fires each time the batch drains — including after more files are added and it drains again — and always reflects the whole batch’s current state, so treat it idempotently (recompute from result; set your Send-enabled flag, don’t toggle it).
Configuring the request
Chainable setters let you configure the batch before (or between) uploads:Each request owns exactly one batch. For separate destinations (e.g. a main conversation and a thread), create a separate
UploadFileRequest for each — their file ids never cross.Adding more files to the batch
To add files incrementally (e.g. the user picks more while earlier uploads are still running), just calluploadAttachments again on the same request — they join the same batch, and onComplete refires when the batch next drains.
Per-call and global listeners
There are two listener scopes, and events fire on both (per-call first):- Per-call listener — the
listeneryou pass touploadAttachment/uploadAttachments. It receives events only for the files in that call. - Global batch listener — registered with
request.addUploadListener(_:). It receives events for every file across all upload calls on the request. There is a single global slot: a lateraddUploadListenerreplaces the previous one;removeUploadListener()clears it.
Reading the batch
Query the request’s current state at any time — useful when the user hits “send”:The attachment getters (
getAttachment, getAttachments, getAttachmentsByType) return only uploaded files, so they’re safe to hand straight to a message’s attachments. getAttachmentCount() counts every file regardless of state, so you can compare it against getAttachments().count to see how many are still pending.Remove, retry & clear
There is no auto-clear on send or logout — call
clearAll() yourself after a successful send (or when abandoning the composer) so the batch doesn’t linger in memory. Re-uploading a failed file’s fileId through uploadAttachment(fileId:file:listener:) also works as a retry if you’d rather supply fresh bytes.Send the uploaded files as a media message
Once your files are uploaded, collect theirAttachments (from request.getAttachments() or from result.successful in onComplete), set them on a MediaMessage built with an empty file URL and no files, and send. One batch can go out as a single multi-attachment message, or you can split the attachments across several messages — e.g. one message per media type using getAttachmentsByType(_:), stamping the same batch id on each send.
A message’s attachments should all match the message’s own type — send mixed picks as one message per kind (images → videos → audios → files) using
getAttachmentsByType(_:). If a batch has more successful uploads than the per-message limit allows (see Limits), split them across multiple MediaMessages. See Multiple Attachments in a Media Message.Limits
Two limits guard the flow, each read from your app settings (configured in the CometChat dashboard, with a built-in fallback):
Read the current limits at runtime — useful for capping your picker’s selection and pre-validating file sizes before uploading:
An oversized or over-count file only rejects that file — the rest of the batch continues uploading. Removing a queued, failed, or uploaded file frees capacity for new uploads.
Reliability behavior
The SDK handles a few transport edge cases for you:- Stalled uploads — if a file makes no progress for 30 seconds, its upload is aborted and reported through
onFileFailurewithERR_UPLOAD_STALLED. Retry it withretryAttachment(fileId:). - Expired upload URLs — each file’s pre-signed upload URL has a limited validity. If it expires before the transfer starts (e.g. the file sat in the queue behind slow uploads), the file fails with
ERR_PRESIGNED_URL_EXPIRED; retrying requests a fresh URL automatically, so retries keep working even after a long delay. - Storage errors — if storage rejects the upload or the network drops, the failure surfaces through
onFileFailurewithERR_S3_UPLOAD_FAILEDand, where available, the transport’s own message. - Batch presign isolation — when a multi-file authorization call fails as a whole, the SDK re-requests each file individually, so one problematic file can’t sink the rest of the batch.
Error handling
Every error delivered toonFileError / onFileFailure is a CometChatException — read errorCode to branch:
Server-side rejections at presign time (e.g. policy or plan denials) carry the server’s own code and message where one is provided —
ERR_PRESIGNED_URL_FAILED is the fallback classification. Files removed via removeAttachment(fileId:) / clearAll() are cancelled silently — they leave the batch without an error callback.Next Steps
Send A Message
Send text, media, and custom messages
Multiple Attachments
Send several attachments in one media message
Receive Messages
Listen for incoming messages in real-time
Threaded Messages
Upload into a thread with setParentMessageId