Resumable uploads
Chunked multipart uploads for large files — the uploadResumable() helper, the low-level multipart primitives, part sizes, and which providers support them.
Pro feature
Resumable/chunked upload is a Radon Pro feature, gated even on free
providers like S3. Set a licenseKey and call await storage.init() before
using any method on this page — otherwise you get LicenseRequiredError.
A resumable upload (also called multipart or chunked) splits a large file into parts and uploads them one at a time, assembling the final object server-side. It's how you move multi-gigabyte files reliably: a failed part retries or aborts without discarding the whole transfer.
The easy way: uploadResumable()
uploadResumable() does the whole dance for you — it starts a multipart upload,
sends each chunk, and completes. If any part fails, it aborts the upload so
no half-finished object is left behind.
await storage.init(); // Pro feature — verifies the license
await storage.uploadResumable(
{ key: "videos/launch.mp4", path: "./launch.mp4" },
{ partSize: 8 * 1024 * 1024 }, // 8 MiB parts (the default)
);It accepts the same UploadInput as upload() —
contentType, metadata, cacheControl, acl, and so on all carry through.
Small files skip multipart automatically
If the object is smaller than one partSize, uploadResumable() transparently
falls back to a single normal upload() — no multipart overhead. You can safely
route all uploads through it.
Part sizes
partSizenumberdefault: 8388608Bytes per part. Default 8 MiB (8 * 1024 * 1024). Ignored when the object is
smaller than one part.
S3's 5 MiB floor
The S3-family stores require every part except the last to be at least
5 MiB. The 8 MiB default sits comfortably above that. If you lower
partSize below 5 MiB, a multi-part upload to an S3-family provider will be
rejected on completion — keep non-final parts at 5 MiB or more.
The low-level primitives
For full control — e.g. uploading browser-selected chunks as they arrive — drive the multipart lifecycle yourself. Four methods, all Pro:
Begin the upload
const mp = await storage.createMultipartUpload("big.bin", {
contentType: "application/octet-stream",
});
// mp = { key, uploadId, provider }Upload each part
Parts are 1-based. Each returns an UploadedPart you must collect.
const p1 = await storage.uploadPart(mp, 1, chunk1);
const p2 = await storage.uploadPart(mp, 2, chunk2);
// p1 = { partNumber, etag, size }Complete it
Hand back the parts; Radon orders them by partNumber and assembles the object.
const result = await storage.completeMultipartUpload(mp, [p1, p2]);
result.key; // "big.bin"Abort on failure
If something goes wrong, discard the uploaded parts so you're not billed for orphaned storage.
try {
// ... upload parts ...
} catch (err) {
await storage.abortMultipartUpload(mp);
throw err;
}The MultipartUpload handle (mp) is opaque — pass it back to each call. It
carries the provider slug, so uploadPart/complete/abort always target the
provider that started the session.
Which providers support it
Multipart is available wherever capabilities.multipart is true:
- The S3 family —
s3,r2,backblaze,spaces,minio,wasabi,linode,vultr,ibm,oracle,scaleway,ceph,storj,filebase,tigris,seaweedfs,gcs— native S3 multipart. - Alibaba OSS — its own multipart protocol (OSS V1-signed).
- Azure Blob — via Put Block / Put Block List.
Providers without multipart — Supabase, local, Vercel Blob, UploadThing,
Bunny, ImageKit, Cloudinary — throw UnsupportedOperationError from
uploadResumable() and the primitives. For those, use a plain
upload() (or a presigned PUT).
Supabase uses TUS, not S3 multipart
Supabase Storage's resumable protocol is TUS-based, not S3 multipart, so its
multipart capability is off. uploadResumable() still works for objects
smaller than one part (it falls back to a single upload), but a large multipart
upload throws UnsupportedOperationError. For big direct uploads to Supabase,
use a presigned upload URL: getUrl(key, { signed: true, method: "PUT" }).
Azure multipart is faux-S3 — abort is a no-op
Azure has no server-side "initiate multipart" call: block ids are chosen
client-side, so createMultipartUpload() doesn't hit the network — it just
returns a local session handle. Consequently abortMultipartUpload() is a
no-op: uncommitted blocks aren't explicitly deleted, but Azure garbage-
collects them automatically after about a week. The uploadPart /
completeMultipartUpload flow works exactly as elsewhere.
Resumable uploads don't fail over
uploadResumable() and the multipart primitives run against the primary
provider — a multipart session is bound to the store that opened it, so it can't
be handed to a failover chain mid-flight.
Signed URLs
Public vs. signed URLs, expiry windows, presigned PUT uploads, forced downloads, and the per-provider support caveats — all through one getUrl() method.
Failover
An ordered chain of storage providers, tried in turn until one succeeds — what fails over, what doesn't (getUrl and list), and how AllProvidersFailedError works.