0
# Multipart Uploads
1
2
Large file upload system supporting files up to 5TB with parallel upload, retry logic, and progress tracking.
3
4
## Overview
5
6
The multipart upload system is designed for large files that exceed single-request upload limits. It splits files into parts (minimum 5MB each, except the last part), uploads them in parallel, and then combines them into a single blob. This approach provides better reliability, progress tracking, and support for very large files.
7
8
## Key Features
9
10
- **Large File Support**: Upload files up to 5TB
11
- **Parallel Uploads**: Upload multiple parts simultaneously for faster transfers
12
- **Progress Tracking**: Monitor upload progress with detailed callbacks
13
- **Resumable Uploads**: Restart failed uploads from where they left off
14
- **Automatic Retry**: Built-in retry logic for failed parts
15
- **Cross-Platform**: Works in Node.js, Edge Runtime, and browsers
16
17
## Capabilities
18
19
### Multipart Upload Lifecycle
20
21
#### createMultipartUpload
22
23
Initiates a multipart upload session.
24
25
```typescript { .api }
26
/**
27
* Initiates a multipart upload process
28
* @param pathname - The pathname to upload the blob to
29
* @param options - Configuration options
30
* @returns Promise resolving to upload session information
31
*/
32
function createMultipartUpload(pathname: string, options: CommonCreateBlobOptions): Promise<MultipartUploadInfo>;
33
34
interface MultipartUploadInfo {
35
key: string;
36
uploadId: string;
37
}
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import { createMultipartUpload } from '@vercel/blob';
44
45
// Start multipart upload
46
const { key, uploadId } = await createMultipartUpload('videos/large-video.mp4', {
47
access: 'public',
48
contentType: 'video/mp4',
49
});
50
51
console.log('Upload session created:', { key, uploadId });
52
```
53
54
#### uploadPart
55
56
Uploads a single part of a multipart upload.
57
58
```typescript { .api }
59
/**
60
* Uploads a part in a multipart upload
61
* @param pathname - Same pathname used in createMultipartUpload
62
* @param body - Part content (minimum 5MB except for the last part)
63
* @param options - Part upload options
64
* @returns Promise resolving to part information
65
*/
66
function uploadPart(pathname: string, body: PutBody, options: UploadPartCommandOptions): Promise<Part>;
67
68
interface UploadPartCommandOptions extends BlobCommandOptions {
69
key: string;
70
uploadId: string;
71
partNumber: number;
72
}
73
74
interface Part {
75
etag: string;
76
partNumber: number;
77
}
78
```
79
80
**Usage Examples:**
81
82
```typescript
83
import { uploadPart } from '@vercel/blob';
84
85
// Upload a single part
86
const part = await uploadPart('videos/large-video.mp4', chunkData, {
87
key: 'abc123',
88
uploadId: 'xyz789',
89
partNumber: 1,
90
});
91
92
console.log('Part uploaded:', part);
93
```
94
95
#### completeMultipartUpload
96
97
Completes a multipart upload by combining all parts.
98
99
```typescript { .api }
100
/**
101
* Completes a multipart upload by combining all parts
102
* @param pathname - Same pathname used in createMultipartUpload
103
* @param parts - Array of uploaded parts in correct order
104
* @param options - Completion options
105
* @returns Promise resolving to blob information
106
*/
107
function completeMultipartUpload(pathname: string, parts: Part[], options: CompleteMultipartUploadCommandOptions): Promise<PutBlobResult>;
108
109
interface CompleteMultipartUploadCommandOptions extends BlobCommandOptions {
110
key: string;
111
uploadId: string;
112
}
113
```
114
115
**Usage Examples:**
116
117
```typescript
118
import { completeMultipartUpload } from '@vercel/blob';
119
120
// Complete the upload
121
const result = await completeMultipartUpload('videos/large-video.mp4', parts, {
122
key: 'abc123',
123
uploadId: 'xyz789',
124
});
125
126
console.log('Upload completed:', result.url);
127
```
128
129
### Simplified Multipart Interface
130
131
#### createMultipartUploader
132
133
Creates a simplified wrapper for multipart uploads that handles the lifecycle automatically.
134
135
```typescript { .api }
136
/**
137
* Creates a simplified multipart uploader wrapper
138
* @param pathname - The pathname to upload the blob to
139
* @param options - Configuration options
140
* @returns Promise resolving to multipart uploader instance
141
*/
142
function createMultipartUploader(pathname: string, options: CommonCreateBlobOptions): Promise<MultipartUploader>;
143
144
interface MultipartUploader {
145
/** Upload a single part by part number */
146
uploadPart(partNumber: number, body: PutBody): Promise<Part>;
147
/** Complete the multipart upload with all parts */
148
complete(parts: Part[]): Promise<PutBlobResult>;
149
/** Abort the multipart upload */
150
abort(): Promise<void>;
151
}
152
```
153
154
**Usage Examples:**
155
156
```typescript
157
import { createMultipartUploader } from '@vercel/blob';
158
159
// Create uploader instance
160
const uploader = await createMultipartUploader('videos/large-video.mp4', {
161
access: 'public',
162
contentType: 'video/mp4',
163
});
164
165
// Upload parts
166
const parts = [];
167
for (let i = 0; i < chunks.length; i++) {
168
const part = await uploader.uploadPart(i + 1, chunks[i]);
169
parts.push(part);
170
}
171
172
// Complete upload
173
const result = await uploader.complete(parts);
174
console.log('Upload completed:', result.url);
175
```
176
177
## Complete Multipart Upload Examples
178
179
### Basic Multipart Upload
180
181
```typescript
182
import {
183
createMultipartUpload,
184
uploadPart,
185
completeMultipartUpload
186
} from '@vercel/blob';
187
188
async function uploadLargeFile(file: File, pathname: string) {
189
// 1. Create multipart upload
190
const { key, uploadId } = await createMultipartUpload(pathname, {
191
access: 'public',
192
contentType: file.type,
193
});
194
195
// 2. Split file into parts (minimum 5MB each)
196
const partSize = 5 * 1024 * 1024; // 5MB
197
const parts: Part[] = [];
198
199
for (let i = 0; i < Math.ceil(file.size / partSize); i++) {
200
const start = i * partSize;
201
const end = Math.min(start + partSize, file.size);
202
const chunk = file.slice(start, end);
203
204
const part = await uploadPart(pathname, chunk, {
205
key,
206
uploadId,
207
partNumber: i + 1,
208
});
209
210
parts.push(part);
211
}
212
213
// 3. Complete multipart upload
214
const result = await completeMultipartUpload(pathname, parts, {
215
key,
216
uploadId,
217
});
218
219
return result;
220
}
221
```
222
223
### Parallel Multipart Upload with Progress
224
225
```typescript
226
import {
227
createMultipartUpload,
228
uploadPart,
229
completeMultipartUpload
230
} from '@vercel/blob';
231
232
interface UploadProgress {
233
totalParts: number;
234
completedParts: number;
235
percentage: number;
236
}
237
238
async function uploadLargeFileWithProgress(
239
file: File,
240
pathname: string,
241
onProgress?: (progress: UploadProgress) => void
242
) {
243
// Create multipart upload
244
const { key, uploadId } = await createMultipartUpload(pathname, {
245
access: 'public',
246
contentType: file.type,
247
});
248
249
// Split file into parts
250
const partSize = 5 * 1024 * 1024; // 5MB
251
const totalParts = Math.ceil(file.size / partSize);
252
const chunks: Blob[] = [];
253
254
for (let i = 0; i < totalParts; i++) {
255
const start = i * partSize;
256
const end = Math.min(start + partSize, file.size);
257
chunks.push(file.slice(start, end));
258
}
259
260
// Upload parts in parallel with progress tracking
261
let completedParts = 0;
262
263
const uploadPromises = chunks.map(async (chunk, index) => {
264
const part = await uploadPart(pathname, chunk, {
265
key,
266
uploadId,
267
partNumber: index + 1,
268
});
269
270
completedParts++;
271
onProgress?.({
272
totalParts,
273
completedParts,
274
percentage: Math.round((completedParts / totalParts) * 100),
275
});
276
277
return part;
278
});
279
280
// Wait for all parts to complete
281
const parts = await Promise.all(uploadPromises);
282
283
// Complete multipart upload
284
const result = await completeMultipartUpload(pathname, parts, {
285
key,
286
uploadId,
287
});
288
289
return result;
290
}
291
292
// Usage
293
const result = await uploadLargeFileWithProgress(
294
largeVideoFile,
295
'videos/my-video.mp4',
296
(progress) => {
297
console.log(`Upload progress: ${progress.percentage}% (${progress.completedParts}/${progress.totalParts} parts)`);
298
}
299
);
300
```
301
302
### Resumable Multipart Upload
303
304
```typescript
305
import {
306
createMultipartUpload,
307
uploadPart,
308
completeMultipartUpload
309
} from '@vercel/blob';
310
311
interface UploadState {
312
key: string;
313
uploadId: string;
314
completedParts: Part[];
315
totalParts: number;
316
}
317
318
async function resumableUpload(
319
file: File,
320
pathname: string,
321
savedState?: UploadState
322
) {
323
let state: UploadState;
324
325
if (savedState) {
326
// Resume existing upload
327
state = savedState;
328
} else {
329
// Start new upload
330
const { key, uploadId } = await createMultipartUpload(pathname, {
331
access: 'public',
332
contentType: file.type,
333
});
334
335
state = {
336
key,
337
uploadId,
338
completedParts: [],
339
totalParts: Math.ceil(file.size / (5 * 1024 * 1024)),
340
};
341
}
342
343
const partSize = 5 * 1024 * 1024; // 5MB
344
const allParts: Part[] = [...state.completedParts];
345
346
// Upload remaining parts
347
for (let i = state.completedParts.length; i < state.totalParts; i++) {
348
try {
349
const start = i * partSize;
350
const end = Math.min(start + partSize, file.size);
351
const chunk = file.slice(start, end);
352
353
const part = await uploadPart(pathname, chunk, {
354
key: state.key,
355
uploadId: state.uploadId,
356
partNumber: i + 1,
357
});
358
359
allParts.push(part);
360
state.completedParts.push(part);
361
362
// Save state for resumption (implement your own storage)
363
await saveUploadState(pathname, state);
364
365
} catch (error) {
366
console.error(`Failed to upload part ${i + 1}:`, error);
367
// Save current state and allow for resumption
368
await saveUploadState(pathname, state);
369
throw error;
370
}
371
}
372
373
// Complete the upload
374
const result = await completeMultipartUpload(pathname, allParts, {
375
key: state.key,
376
uploadId: state.uploadId,
377
});
378
379
// Clear saved state
380
await clearUploadState(pathname);
381
382
return result;
383
}
384
385
// Helper functions (implement based on your storage needs)
386
async function saveUploadState(pathname: string, state: UploadState) {
387
localStorage.setItem(`upload_${pathname}`, JSON.stringify(state));
388
}
389
390
async function clearUploadState(pathname: string) {
391
localStorage.removeItem(`upload_${pathname}`);
392
}
393
394
async function getUploadState(pathname: string): Promise<UploadState | undefined> {
395
const saved = localStorage.getItem(`upload_${pathname}`);
396
return saved ? JSON.parse(saved) : undefined;
397
}
398
```
399
400
### Using the Simplified Uploader
401
402
```typescript
403
import { createMultipartUploader } from '@vercel/blob';
404
405
async function uploadWithSimplifiedInterface(file: File, pathname: string) {
406
// Create uploader
407
const uploader = await createMultipartUploader(pathname, {
408
access: 'public',
409
contentType: file.type,
410
});
411
412
try {
413
// Split and upload parts
414
const partSize = 5 * 1024 * 1024; // 5MB
415
const parts: Part[] = [];
416
417
for (let i = 0; i < Math.ceil(file.size / partSize); i++) {
418
const start = i * partSize;
419
const end = Math.min(start + partSize, file.size);
420
const chunk = file.slice(start, end);
421
422
const part = await uploader.uploadPart(i + 1, chunk);
423
parts.push(part);
424
}
425
426
// Complete upload
427
const result = await uploader.complete(parts);
428
return result;
429
430
} catch (error) {
431
// Abort upload on error
432
await uploader.abort();
433
throw error;
434
}
435
}
436
```
437
438
## Common Types
439
440
### Part Input for Custom Uploaders
441
442
```typescript { .api }
443
interface PartInput {
444
partNumber: number;
445
blob: PutBody;
446
}
447
```
448
449
### Multipart Helper Types
450
451
```typescript { .api }
452
interface MultipartUploadInfo {
453
key: string;
454
uploadId: string;
455
}
456
457
interface Part {
458
etag: string;
459
partNumber: number;
460
}
461
462
interface MultipartUploader {
463
uploadPart(partNumber: number, body: PutBody): Promise<Part>;
464
complete(parts: Part[]): Promise<PutBlobResult>;
465
abort(): Promise<void>;
466
}
467
```
468
469
## Best Practices
470
471
### File Size Considerations
472
473
- **Small files (< 4.5MB)**: Use regular `put()` function for simplicity
474
- **Medium files (4.5MB - 100MB)**: Use `put()` with `multipart: true` option
475
- **Large files (> 100MB)**: Use dedicated multipart upload functions for better control
476
477
### Part Size Guidelines
478
479
- **Minimum part size**: 5MB (except for the last part)
480
- **Maximum part size**: 5GB
481
- **Recommended part size**: 10-100MB for optimal performance
482
- **Total parts limit**: 10,000 parts per upload
483
484
### Error Handling
485
486
```typescript
487
import {
488
createMultipartUpload,
489
uploadPart,
490
completeMultipartUpload,
491
BlobError
492
} from '@vercel/blob';
493
494
async function robustMultipartUpload(file: File, pathname: string) {
495
let uploadId: string;
496
let key: string;
497
498
try {
499
// Create upload
500
const result = await createMultipartUpload(pathname, {
501
access: 'public',
502
});
503
uploadId = result.uploadId;
504
key = result.key;
505
506
// Upload parts with retry logic
507
const parts = await uploadPartsWithRetry(file, pathname, key, uploadId);
508
509
// Complete upload
510
return await completeMultipartUpload(pathname, parts, {
511
key,
512
uploadId,
513
});
514
515
} catch (error) {
516
if (error instanceof BlobError) {
517
console.error('Blob service error:', error.message);
518
}
519
520
// Clean up failed upload if possible
521
if (uploadId && key) {
522
try {
523
// Note: There's no explicit abort function in the public API
524
// The upload will be cleaned up automatically after some time
525
console.log('Upload failed, will be cleaned up automatically');
526
} catch (cleanupError) {
527
console.error('Cleanup failed:', cleanupError);
528
}
529
}
530
531
throw error;
532
}
533
}
534
535
async function uploadPartsWithRetry(
536
file: File,
537
pathname: string,
538
key: string,
539
uploadId: string,
540
maxRetries = 3
541
): Promise<Part[]> {
542
const partSize = 5 * 1024 * 1024;
543
const parts: Part[] = [];
544
545
for (let i = 0; i < Math.ceil(file.size / partSize); i++) {
546
const start = i * partSize;
547
const end = Math.min(start + partSize, file.size);
548
const chunk = file.slice(start, end);
549
550
let attempts = 0;
551
while (attempts < maxRetries) {
552
try {
553
const part = await uploadPart(pathname, chunk, {
554
key,
555
uploadId,
556
partNumber: i + 1,
557
});
558
parts.push(part);
559
break;
560
} catch (error) {
561
attempts++;
562
if (attempts >= maxRetries) {
563
throw new Error(`Failed to upload part ${i + 1} after ${maxRetries} attempts: ${error}`);
564
}
565
// Exponential backoff
566
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempts) * 1000));
567
}
568
}
569
}
570
571
return parts;
572
}
573
```