A resumable upload lets a web app continue sending a large file after a network interruption instead of making the user start over. It adds server state, cleanup work, and more failure cases, so use it when file size or unreliable connections make a single request a poor fit.
For small, bounded files, a normal multipart/form-data request is usually easier to implement and operate. For larger uploads, choose a resumable protocol or your object-storage provider's multipart API, then make the server—not the browser—the authority on who may upload, how much has arrived, and when the result is complete.
Choose an upload approach
| Approach | A good fit when | Main tradeoff |
|---|---|---|
| Single request | Files are small, request limits are predictable, and users can retry from the beginning | A dropped connection usually means sending the file again |
| tus | You want a standard HTTP protocol and control the upload endpoint or can use a compatible service | You need a tus server and must manage upload records, storage, and expiration |
| Object-storage upload | Files should go directly to a storage provider, especially when your app server should not relay the bytes | The client and server must coordinate provider-specific sessions, part URLs, completion, and cleanup |
These approaches are not interchangeable APIs. tus defines a resumable-upload protocol; Amazon S3 multipart upload and Google Cloud Storage resumable upload are provider APIs with their own request formats and limits. Check the current provider documentation for object-size, part-size, concurrency, checksum, and expiration constraints before you build around them.
Design the upload lifecycle first
Treat an upload as a short-lived resource with an owner, a destination, an expected size, and a state. A typical flow has five steps:
- Create a session. An authenticated request tells your server the intended file size and purpose. Check the user's authorization, quota, and allowed size before allocating storage. Return an opaque upload ID and an expiration time.
- Send chunks. The browser slices a
Fileinto chunks. Upload them sequentially when the protocol needs an offset, or in parallel when the storage API supports independently numbered parts and the extra concurrency is useful. - Resume after interruption. Keep the upload ID, but ask the server or provider how much data it has accepted. Resume from that confirmed position; do not trust a progress value saved only in the browser.
- Finalize. The server verifies the session, expected size, uploaded parts, and any checksums before marking the object complete or making it available to the rest of the app.
- Expire or cancel. Remove incomplete sessions and temporary data when the user cancels or the session expires. A successful browser request is not a substitute for server-side cleanup.
Keep session metadata—owner, object key, expected size, expiry, and state—in a database or another durable store if uploads need to survive a process restart. Do not keep the only copy of that information in a web server's memory.
tus and object-storage multipart uploads
Use tus when you want a protocol-level offset
The tus protocol uses an upload resource and a confirmed byte offset. A client can query the resource with HEAD, then continue with PATCH at the returned Upload-Offset. If the client sends an unexpected offset, the server rejects the request rather than silently writing at the wrong position. This makes recovery after a lost response easier to reason about.
tus also defines optional extensions for checksums, upload expiration, termination, and concatenating partial uploads. Those are capabilities a server advertises; do not assume every tus service implements every extension. Check the server's Tus-Extension response and configure client behavior accordingly.
Choose tus when you want clients and servers to share a standard upload contract instead of designing your own chunk protocol. Your server still needs to map sessions to authenticated users, store the bytes, and remove abandoned uploads.
Use provider APIs when direct-to-storage uploads fit
With an object-storage flow, your backend creates an upload session and authorizes the browser to send data to storage. For S3 multipart upload, the browser uploads numbered parts and the backend completes the upload using the part information returned by storage. Parts can be sent independently, which can make parallel transfer practical, but your app must track the upload ID and completion data. Configure a lifecycle rule or a backend cleanup job to abort unfinished multipart uploads.
Google Cloud Storage's resumable upload API returns a session URI that a browser can use to send the object in one or more requests. Google documents that a session can remain active for up to a week. Treat the session URI as a bearer credential: send it only over HTTPS, keep it out of logs and analytics, and avoid exposing it beyond the user who owns the upload.
Direct-to-storage uploads keep file bytes off your application server, but they do not remove the need for server-side authorization. Use short-lived, narrowly scoped upload permissions; bind each session to an authenticated user and a server-generated object key; and let the backend decide whether the uploaded object is accepted.
Make retries and integrity checks reliable
Networks can fail after storage has accepted a chunk but before the browser receives the response. On reconnect, reconcile with the server or provider before resending data. For tus, query the current offset and continue from it. For a multipart API, retry the same part identifier when the API allows it, and use the provider's authoritative part list when completing the upload.
Retry transient network errors and suitable server errors with a bounded delay and backoff. Do not retry every error: authorization failures, invalid sizes, and expired sessions need a clear error or a new session. Make finalization idempotent so that a repeated completion request returns the existing result instead of creating a second object or processing the file twice.
Use checksums when your protocol and storage provider support them, and verify the completed object before processing it. Do not assume an object-storage ETag is a whole-file MD5 checksum; multipart ETags may not represent one. If you calculate a checksum in the browser, account for memory use rather than reading a very large file into memory all at once.
Handle progress, cancellation, and cleanup
Show two kinds of progress separately: bytes sent by the browser and bytes confirmed by the server. The confirmed offset is what matters for resuming after a tab closes or a connection drops.
The browser's AbortController can cancel an in-flight fetch request. That stops the client-side operation, but it does not guarantee that the server has deleted the upload or that an object-storage multipart session has been aborted. Add an authenticated cancel operation that marks the session as cancelled and triggers the appropriate server or provider cleanup.
Set an expiration when a session is created. A cleanup task should find expired or cancelled sessions, abort unfinished provider uploads, and remove temporary objects and metadata. Use both application cleanup and storage-provider lifecycle rules where available; lifecycle rules are a useful backstop, not a replacement for showing users an accurate upload state.
Protect the upload endpoint and stored files
Apply the same checks to resumed chunks and finalization as to session creation:
- Require authorization and verify that the session belongs to the current user. An unguessable ID is not an authorization check.
- Enforce a maximum file size, storage quota, and allowed file types on the server. Do not trust a filename, extension, or browser-supplied
Content-Typeas proof of file contents. - Generate storage keys on the server instead of using a user-supplied path. Keep unfinished files private and outside any public web root.
- Validate the completed file before processing or serving it. Depending on the file and your product, that can include checking its signature, scanning it, and serving it with safe content headers.
- Scope signed upload URLs to the intended object and operation, and give them only the lifetime they need. Keep cloud credentials on the server, never in browser code.
The OWASP File Upload Cheat Sheet covers file validation, safe names, storage location, permissions, and upload limits in more detail.
A practical default
Start with a single request if your maximum file size is modest and your server, proxy, and hosting limits are clear. Add resumability only when failed retries or large transfers create a real user problem.
If your application owns the upload endpoint and needs a standard offset-based protocol, evaluate tus. If your files belong in object storage and you want browsers to send bytes directly there, use that provider's resumable or multipart API and keep session creation, authorization, finalization, and cleanup on your backend.
Whichever path you choose, test interruption at different points, repeated completion requests, expired sessions, and cancellation. Those cases determine whether an upload is genuinely resumable and safe to operate—not just divided into chunks.