Skip to main content
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 FileList, 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([{ fileId, file }], listener). Each file needs a caller-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() / request.retryAttachment() 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 with setAttachments([...]) 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() accepts:
The recipient must match the message you’ll eventually send. receiverId / receiverType are sent to the upload-authorization (presign) endpoint, which enforces the sender’s role- and scope-based access control for that conversation before any bytes transfer. If the sender isn’t allowed to send media to that user or group, each file is rejected via onFileError with ERR_PERMISSION_DENIED (not retryable). Pass the same recipient you’ll set on the MediaMessage at send time.

Upload files

Build an UploadFileListener and call uploadAttachments() (or uploadAttachment() for a single file). Each item pairs a file with a required, caller-supplied fileId.
fileId is caller-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. A fileId already present in the batch is silently skipped (deduped), so re-adding the same file is a no-op. file must be a platform-native File/Blob (the pipeline needs a numeric size).Validation, presigning, and the byte transfer all run asynchronously after uploadAttachments() returns — track outcomes through the listener. Each file is presigned and uploaded independently, so one file being rejected or failing never affects the others in the batch.

The UploadFileListener

Pass an UploadFileListener with only the callbacks you need — all are optional. Unlike MessageListener or CallListener, it is not registered globally with a string id; it lives for the duration of the upload batch.
Rejected vs. failed — the key distinction. onFileError (rejected) means the request itself is unacceptable — invalid file object, over the size limit, or the sender isn’t permitted to upload to this recipient (ERR_PERMISSION_DENIED). Retrying won’t help; the user must swap the file or you must fix the recipient/permissions. onFileFailure (failed) means a transient transport problem — network drop, an expired presigned URL, or a stalled upload. These can be retried with retryAttachment().

UploadResult

onComplete receives the batch’s settled state. It fires each time the batch drains — including after a retry adds new work and it drains again.

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 call uploadAttachments() again on the same request — they join the same batch. Re-adding a fileId already in the batch is skipped.

Per-call and global listeners

There are two listener scopes, and events fire on both:
  • Per-call listener — the listener you pass to uploadAttachment() / uploadAttachments(). It receives events only for the files in that call.
  • Global batch listener — registered with request.addUploadListener(listener). It receives events for every file across all upload calls on the request. There is a single global slot: a later addUploadListener() overrides 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 setAttachments(). getAttachmentCount() counts every file regardless of state, so you can compare it against getAttachments().length to see how many are still pending.

Remove, retry & clear

All active upload batches are also cleared automatically on CometChat.logout(). Since there is no send hook, call clearAll() yourself after a successful send so a batch doesn’t linger in memory until logout.

Send the uploaded files as a media message

Drive the send from an explicit user action (a “Send” button) — read the uploaded Attachments from the request, build one or more MediaMessages, and send. Splitting the batch into one MediaMessage per media type (via getAttachmentsByType()) keeps each message’s type aligned with its content, so images arrive as image messages, files as file messages, and so on.
Send from a button, not from onComplete. onComplete fires every time the batch drains — including after a retry re-drains it. Sending there would ship whatever happened to be uploaded on that drain and (with clearAll()) discard the files the user was about to retry. Gate an explicit “Send” action on the batch being fully settled instead: at least one file, and every staged file uploaded (nothing pending, failed, or rejected).
sendMediaMessage() enforces the maximum attachments per message (see Limits). The per-type split above already keeps message types correct, but it does not guarantee each message is under the count limit — if a single type has more attachments than file.count.max allows, chunk that type across multiple MediaMessages too. See Multiple Attachments in a Media Message.

Limits

Two independent checks guard the flow, each using a limit read from your app settings (with a built-in fallback): Read the current attachment-count limit at runtime with CometChat.getMaxAttachmentCount() — useful for disabling the “add file” button in your composer once the user hits the cap:
The per-file size limit is checked at upload time (before the byte transfer), while the attachment count limit is checked in sendMediaMessage() (when you send). A batch can therefore upload successfully but still be rejected at send time if it exceeds the count limit — plan your composer around both.

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 onFileFailure with ERR_UPLOAD_STALLED. Retry it with retryAttachment().
  • Expired upload URLs — the pre-signed upload URL for each file is valid for 15 minutes. retryAttachment() automatically requests a fresh URL if the original has expired, so retries keep working even after a long delay. If that fresh request is permanently denied (e.g. permissions changed), the file moves to rejected (onFileError) rather than looping as a failure.
  • Storage errors — if storage rejects the upload, the failure surfaces through onFileFailure with ERR_S3_UPLOAD_FAILED and, where available, the storage’s own message.

Error handling

Every error delivered to onFileError / onFileFailure (and rejections from sendMediaMessage()) is a CometChatException. The upload-specific codes:
ERR_PERMISSION_DENIED and ERR_BAD_REQUEST are returned by the server’s presign check, so their exact code and message come from your backend. See the full list on the Error Codes page.

Next Steps

Send A Message

Send text, media, and custom messages

Multiple Attachments

Send several attachments in one media message

Error Codes

Media upload error reference

Receive Messages

Listen for incoming messages in real-time