Multipart Upload
Upload large files in chunks with progress tracking.
Usage
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- Python
- Go
- PHP
- Rust
- C#
- C++
const bucket = client.storage.bucket('videos');
await bucket.upload('presentation.mp4', largeFile, {
contentType: 'video/mp4',
onProgress: (progress) => {
console.log(`Upload: ${progress.percent}%`);
},
});
final bucket = client.storage.bucket('videos');
await bucket.upload(
'presentation.mp4',
largeFileBytes,
contentType: 'video/mp4',
onProgress: (sent, total) => print('$sent / $total'),
);
let bucket = client.storage.bucket("videos")
try await bucket.upload(
"presentation.mp4",
data: largeFileData,
contentType: "video/mp4"
)
val bucket = client.storage.bucket("videos")
bucket.upload(
"presentation.mp4",
largeFileBytes,
contentType = "video/mp4"
)
StorageBucket bucket = client.storage().bucket("videos");
bucket.upload("presentation.mp4", largeFileBytes, "video/mp4");
bucket = client.storage.bucket('videos')
bucket.upload('presentation.mp4', large_file_bytes, content_type='video/mp4')
bucket := admin.Storage.Bucket("videos")
result, err := bucket.Upload("presentation.mp4", largeFileData, "video/mp4")
$bucket = $client->storage->bucket('videos');
$result = $bucket->upload('presentation.mp4', $largeFileData, 'video/mp4');
let bucket = client.storage().bucket("videos");
let result = bucket.upload("presentation.mp4", &large_file_data, "video/mp4").await?;
var bucket = admin.Storage.Bucket("videos");
var result = await bucket.UploadAsync("presentation.mp4", largeFileBytes, "video/mp4");
auto bucket = client.storage().bucket("videos");
auto result = bucket.upload("presentation.mp4", data, "video/mp4");
The SDK automatically:
- Splits files larger than 5MB into parts
- Uploads parts in parallel
- Reports combined progress
- Completes the multipart upload on the server
Resume Support
If a multipart upload fails mid-way, the SDK throws a ResumableUploadError containing the uploadId and key needed to resume:
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- Python
- Go
- PHP
- Rust
- C#
- C++
import { ResumableUploadError } from '@edgebase/client';
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);
}
}
try {
await bucket.upload('large-video.mp4', file);
} on ResumableUploadError catch (error) {
print('Failed at part ${error.failedPartNumber}, resuming...');
final result = await bucket.resumeUpload(
error.key,
error.uploadId,
file,
);
print('Upload completed: ${result.key}');
}
do {
try await bucket.upload("large-video.mp4", data: fileData)
} catch let error as ResumableUploadError {
print("Failed at part \(error.failedPartNumber), resuming...")
let result = try await bucket.resumeUpload(
key: error.key,
uploadId: error.uploadId,
data: fileData
)
print("Upload completed: \(result.key)")
}
try {
bucket.upload("large-video.mp4", fileBytes)
} catch (error: ResumableUploadError) {
println("Failed at part ${error.failedPartNumber}, resuming...")
val result = bucket.resumeUpload(
error.key,
error.uploadId,
fileBytes
)
println("Upload completed: ${result.key}")
}
try {
bucket.upload("large-video.mp4", fileBytes, "video/mp4");
} catch (ResumableUploadError error) {
System.out.println("Failed at part " + error.getFailedPartNumber() + ", resuming...");
FileInfo result = bucket.resumeUpload(
error.getKey(), error.getUploadId(), fileBytes
);
System.out.println("Upload completed: " + result.getKey());
}
from edgebase import ResumableUploadError
try:
bucket.upload('large-video.mp4', file_bytes)
except ResumableUploadError as error:
print(f'Failed at part {error.failed_part_number}, resuming...')
result = bucket.resume_upload(error.key, error.upload_id, file_bytes)
print(f'Upload completed: {result.key}')
result, err := bucket.Upload("large-video.mp4", fileData, "video/mp4")
if resumeErr, ok := err.(*edgebase.ResumableUploadError); ok {
fmt.Printf("Failed at part %d, resuming...\n", resumeErr.FailedPartNumber)
result, err = bucket.ResumeUpload(resumeErr.Key, resumeErr.UploadID, fileData)
}
try {
$bucket->upload('large-video.mp4', $fileData, 'video/mp4');
} catch (ResumableUploadError $error) {
echo "Failed at part {$error->failedPartNumber}, resuming...\n";
$result = $bucket->resumeUpload($error->key, $error->uploadId, $fileData);
}
match bucket.upload("large-video.mp4", &file_data, "video/mp4").await {
Err(Error::ResumableUpload(error)) => {
println!("Failed at part {}, resuming...", error.failed_part_number);
let result = bucket.resume_upload(&error.key, &error.upload_id, &file_data).await?;
println!("Upload completed: {}", result.key);
}
other => other?,
}
try
{
await bucket.UploadAsync("large-video.mp4", fileBytes, "video/mp4");
}
catch (ResumableUploadException error)
{
Console.WriteLine($"Failed at part {error.FailedPartNumber}, resuming...");
var result = await bucket.ResumeUploadAsync(error.Key, error.UploadId, fileBytes);
Console.WriteLine($"Upload completed: {result.Key}");
}
try {
auto result = bucket.upload("large-video.mp4", data, "video/mp4");
} catch (const ResumableUploadError& error) {
std::cout << "Failed at part " << error.failedPartNumber << ", resuming..." << std::endl;
auto result = bucket.resumeUpload(error.key, error.uploadId, data);
}
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).
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:
| Endpoint | Description |
|---|---|
POST /api/storage/:bucket/multipart/create | Initiate upload |
POST /api/storage/:bucket/multipart/upload-part?uploadId=...&partNumber=...&key=... | Upload a part |
POST /api/storage/:bucket/multipart/complete | Complete upload |
POST /api/storage/:bucket/multipart/abort | Abort 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