This page covers different methods to upload files to your Blob Storage, from simple file uploads to large file multipart uploads.
For most use cases, you can use handleUpload() on the server and useUpload() on the client for a streamlined file upload experience.
This example create an API route to handle file uploads on /api/upload. It validates the files to be images of less than 10MB, and uploads them to Blob Storage with a random suffix and the images prefix.
import { blob } from 'hub:blob'
export default eventHandler(async (event) => {
// Make sure to check if the user can upload files before calling this function
return blob.handleUpload(event, {
formKey: 'files',
multiple: true,
ensure: {
maxSize: '10MB',
types: ['image/jpeg', 'image/png', 'image/webp'],
},
put: {
addRandomSuffix: true,
prefix: 'images',
},
})
})
handleUpload()A server function to handle file uploads. It validates the files and uploads them to Blob Storage.
await blob.handleUpload(event, options)
'files'.true, accepts multiple files and returns an array of BlobObject. Defaults to true.ensureBlob().blob.put().Returns a BlobObject or an array of BlobObject if multiple is true.
Throws an error if the file doesn't meet the requirements.
useUpload()A Vue composable to handle file uploads on the client.
<script setup lang="ts">
const upload = useUpload('/api/upload')
async function onFileSelect(event: Event) {
const target = event.target as HTMLInputElement
const uploadedFiles = await upload(target)
// Files uploaded successfully
console.log('Uploaded:', uploadedFiles)
}
</script>
<template>
<input
accept="image/jpeg, image/png, image/webp"
type="file"
name="files"
multiple
@change="onFileSelect"
>
</template>
'files'.true.'POST'.Returns a function that accepts an HTMLInputElement and returns a Promise resolving to the uploaded blob data.
import { blob } from 'hub:blob'
export default eventHandler(async (event) => {
return blob.handleUpload(event, {
formKey: 'files',
multiple: true,
ensure: {
maxSize: '10MB',
types: ['image'],
},
put: {
addRandomSuffix: true,
prefix: 'avatars',
},
})
})
<script setup lang="ts">
const upload = useUpload('/api/upload')
const loading = ref(false)
async function onFileSelect(event: Event) {
const target = event.target as HTMLInputElement
if (!target.files?.length) return
loading.value = true
try {
const uploadedFiles = await upload(target)
window.alert(`Uploaded ${uploadedFiles.length} file(s)`)
} catch (error) {
window.alert(`Upload failed: ${error.message}`)
} finally {
loading.value = false
target.value = '' // Reset input
}
}
</script>
<template>
<div>
<input
accept="image/*"
type="file"
name="files"
multiple
:disabled="loading"
@change="onFileSelect"
>
<p v-if="loading">Uploading...</p>
</div>
</template>
For large files (typically > 10MB), use multipart uploads to split the file into smaller chunks. This provides better reliability and allows you to track upload progress.
handleMultipartUpload()A server function to handle multipart upload requests. It handles creating, uploading parts, completing, and aborting multipart uploads.
import { blob } from 'hub:blob'
export default eventHandler(async (event) => {
return blob.handleMultipartUpload(event)
})
[action] and [...pathname] params. The action param handles the different multipart operations (create, upload, complete, abort).true, a random suffix will be added to the blob's name. Defaults to false.Returns a response based on the action:
create: Returns { pathname, uploadId }upload: Returns { partNumber, etag }complete: Returns a BlobObjectabort: Returns nothinguseMultipartUpload()A Vue composable to handle multipart file uploads on the client with progress tracking.
<script setup lang="ts">
const mpu = useMultipartUpload('/api/files/multipart')
const file = ref<File | null>(null)
const progress = ref(0)
const uploading = ref(false)
async function uploadFile() {
if (!file.value) return
uploading.value = true
const { completed, progress: uploadProgress, abort } = mpu(file.value)
// Track progress
watch(uploadProgress, (value) => {
progress.value = value
})
try {
const blob = await completed
console.log('Upload complete:', blob)
} catch (error) {
console.error('Upload failed:', error)
} finally {
uploading.value = false
}
}
</script>
<template>
<div>
<input type="file" @change="(e) => file = e.target.files?.[0]">
<button @click="uploadFile" :disabled="!file || uploading">
Upload
</button>
<div v-if="uploading">
Progress: {{ Math.round(progress * 100) }}%
</div>
</div>
</template>
handleMultipartUpload().10MB.1.3.query and headers will be merged.Returns a function that accepts a File and returns:
{
completed: Promise<BlobObject | undefined> // Resolves when upload completes
progress: Readonly<Ref<number>> // Upload progress (0 to 1)
abort: () => Promise<void> // Function to cancel the upload
}
useMultipartUpload() automatically uses the Vercel Blob Client SDK for uploads.import { blob } from 'hub:blob'
export default eventHandler(async (event) => {
return blob.handleMultipartUpload(event, {
addRandomSuffix: true,
prefix: 'uploads',
})
})
<script setup lang="ts">
const mpu = useMultipartUpload('/api/files/multipart', {
partSize: 10 * 1024 * 1024, // 10MB parts
concurrent: 3, // Upload 3 parts at a time
})
const file = ref<File | null>(null)
const progress = ref(0)
const uploading = ref(false)
let abortUpload: (() => Promise<void>) | null = null
async function startUpload() {
if (!file.value) return
uploading.value = true
progress.value = 0
const { completed, progress: uploadProgress, abort } = mpu(file.value)
abortUpload = abort
watch(uploadProgress, (value) => {
progress.value = value
})
try {
const blob = await completed
console.log('Uploaded:', blob)
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Upload failed:', error)
}
} finally {
uploading.value = false
abortUpload = null
}
}
async function cancelUpload() {
if (abortUpload) {
await abortUpload()
uploading.value = false
}
}
</script>
<template>
<div>
<input
type="file"
:disabled="uploading"
@change="(e) => file = e.target.files?.[0]"
>
<button v-if="!uploading" @click="startUpload" :disabled="!file">
Upload
</button>
<button v-else @click="cancelUpload">
Cancel
</button>
<div v-if="uploading" class="progress-bar">
<div :style="{ width: `${progress * 100}%` }" />
<span>{{ Math.round(progress * 100) }}%</span>
</div>
</div>
</template>
For more control over the multipart upload process, you can use the low-level APIs directly.
handleMultipartUpload() and useMultipartUpload() for most use cases. Use these low-level APIs only when you need custom behavior.createMultipartUpload()Start a new multipart upload.
import { blob } from 'hub:blob'
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
const mpu = await blob.createMultipartUpload(pathname, {
addRandomSuffix: true,
})
return {
uploadId: mpu.uploadId,
pathname: mpu.pathname,
}
})
true, a random suffix will be added to the pathname. Defaults to false.Returns a BlobMultipartUpload object.
resumeMultipartUpload()Resume an existing multipart upload to upload parts, complete, or abort it.
Upload a part:
import { blob } from 'hub:blob'
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
const { uploadId, partNumber } = getQuery(event)
const body = await readRawBody(event)
const mpu = blob.resumeMultipartUpload(pathname, uploadId as string)
return mpu.uploadPart(Number(partNumber), body)
})
Complete the upload:
import { blob } from 'hub:blob'
export default eventHandler(async (event) => {
const { pathname, uploadId } = getQuery(event)
const parts = await readBody(event)
const mpu = blob.resumeMultipartUpload(pathname as string, uploadId as string)
return mpu.complete(parts)
})
Abort the upload:
import { blob } from 'hub:blob'
export default eventHandler(async (event) => {
const { pathname } = getRouterParams(event)
const { uploadId } = getQuery(event)
const mpu = blob.resumeMultipartUpload(pathname, uploadId as string)
await mpu.abort()
return sendNoContent(event)
})
createMultipartUpload().Returns a BlobMultipartUpload object with methods:
uploadPart(partNumber, body): Upload a single partcomplete(parts): Complete the upload with all uploaded partsabort(): Cancel the uploadasync function uploadLargeFile(file: File) {
const chunkSize = 10 * 1024 * 1024 // 10MB per part
// 1. Create the multipart upload
const { pathname, uploadId } = await $fetch(`/api/files/multipart/${file.name}`, {
method: 'POST',
})
// 2. Upload parts
const totalParts = Math.ceil(file.size / chunkSize)
const uploadedParts = []
for (let i = 0; i < totalParts; i++) {
const start = i * chunkSize
const end = Math.min(start + chunkSize, file.size)
const chunk = file.slice(start, end)
const partNumber = i + 1
const part = await $fetch(`/api/files/multipart/${pathname}`, {
method: 'PUT',
query: { uploadId, partNumber },
body: chunk,
})
uploadedParts.push(part)
console.log(`Uploaded part ${partNumber}/${totalParts}`)
}
// 3. Complete the upload
const blob = await $fetch('/api/files/multipart/complete', {
method: 'POST',
query: { pathname, uploadId },
body: uploadedParts,
})
return blob
}
BlobObject{
pathname: string
contentType: string | undefined
size: number
httpEtag: string | undefined
uploadedAt: Date
httpMetadata: Record<string, string>
customMetadata: Record<string, string>
url: string | undefined
}
BlobMultipartUpload{
pathname: string
uploadId: string
uploadPart(
partNumber: number,
value: string | ReadableStream<any> | ArrayBuffer | ArrayBufferView | Blob
): Promise<BlobUploadedPart>
abort(): Promise<void>
complete(uploadedParts: BlobUploadedPart[]): Promise<BlobObject>
}
BlobUploadedPart{
partNumber: number
etag: string
}
MultipartUploader(file: File) => {
completed: Promise<BlobObject | undefined>
progress: Readonly<Ref<number>>
abort: () => Promise<void>
}