Skip to main content

Multipart Upload

Beta

This feature is in beta. Core behavior is stable and ready to try, but some APIs or configuration may still evolve before general availability.

Upload large files in chunks with progress tracking.

Usage

const bucket = client.storage.bucket('videos');

await bucket.upload('presentation.mp4', largeFile, {
contentType: 'video/mp4',
onProgress: (progress) => {
console.log(`Upload: ${progress.percent}%`);
},
});

The SDK automatically:

  1. Splits files larger than 5MB into parts
  2. Uploads parts in parallel
  3. Reports combined progress
  4. Completes the multipart upload on the server

The multipart contentType is normalized before it is stored. Completed files use the same download safety policy as single-request uploads: active or unknown types are forced to opaque sandboxed attachments, while explicitly passive media can render inline.

Resume Support

If a multipart upload fails mid-way, the SDK throws a ResumableUploadError containing the uploadId and key needed to resume:

import { ResumableUploadError } from '@edge-base/web';

try {
await bucket.upload('large-video.mp4', file, {
onProgress: (p) => console.log(`${p.percent}%`),
});
} catch (error) {
if (error instanceof ResumableUploadError) {
console.log(`Failed at part ${error.failedPartNumber}, resuming...`);

// Resume — only uploads the remaining parts
const result = await bucket.resumeUpload(
error.key,
error.uploadId,
file, // same file reference
);
console.log('Upload completed:', result.key);
}
}

Query Uploaded Parts

You can check which parts have been uploaded for an in-progress multipart upload:

const { parts } = await bucket.getUploadParts('large-video.mp4', uploadId);
// parts: [{ partNumber: 1, etag: '...' }, { partNumber: 2, etag: '...' }, ...]

Part tracking data is stored in KV with a 7-day TTL (synced with R2's auto-abort window).

Signed upload URLs can authorize multipart requests too. Pass the signed URL's token and key query parameters to multipart/create, multipart/upload-part, multipart/complete, multipart/abort, and uploads/:uploadId/parts when the client should continue without auth headers after the original write rule check.

Before EdgeBase asks R2 to create a session, it atomically claims the token's single-use grant. Only the winning request can create an R2 session; competing or repeated creates cannot fan out abandoned sessions. EdgeBase then compare-and-swap binds that claim to the returned upload ID. The same token can list and upload parts, complete, or abort only that bound upload ID; switching upload IDs and using the token for a single-file upload are rejected. A create, bind, abort, or later session failure does not return the grant because storage failures can be ambiguous. Request a new signed upload URL before starting over.

If the signed URL sets maxFileSize, every part must include a positive Content-Length. EdgeBase atomically reserves each declared part length against one aggregate grant budget before the R2 part write. Parallel requests cannot push the reservation above the limit; an over-budget attempt terminally closes and aborts the session. Retries and part replacements reserve bytes again, so use a new signed upload URL after an ambiguous part failure.

For signed multipart uploads, the token must be valid when EdgeBase authorizes multipart/complete. A completion admitted before expiresAt may finish afterward; a new continuation request at or after the boundary is rejected. EdgeBase does not perform a racy post-commit delete that could remove a newer trusted write to the same key.

Cancel Upload

Multipart uploads can be cancelled mid-flight using .cancel():

const task = bucket.upload('large-video.mp4', file, {
onProgress: (p) => console.log(`${p.percent}%`),
});

// Cancel after 10 seconds
setTimeout(() => task.cancel(), 10_000);

try {
await task;
} catch (err) {
if (err.name === 'AbortError') {
console.log('Upload cancelled');
}
}

Cancelled multipart uploads are automatically cleaned up by R2 after 7 days. resumeUpload() also returns a cancellable UploadTask.

R2 Multipart API

Under the hood, EdgeBase uses R2's Multipart Upload API:

EndpointDescription
POST /api/storage/:bucket/multipart/createInitiate upload
POST /api/storage/:bucket/multipart/upload-part?uploadId=...&partNumber=...&key=...Upload a part
POST /api/storage/:bucket/multipart/completeComplete upload
POST /api/storage/:bucket/multipart/abortAbort upload
GET /api/storage/:bucket/uploads/:uploadId/parts?key=...Fetch uploaded parts for resume

Limits

  • Minimum part size: 5MB (except the last part)
  • Maximum parts: 10,000