Radondocs

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.

Upload a large video in 8 MiB parts
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: 8388608

Bytes 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 familys3, 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.

On this page