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 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.

On this page