File uploads

Upload files with validation, simple form handlers, and multipart flows using `@vitehub/blob`.

ViteHub Blob supports upload workflows from validated form uploads to large multipart uploads.

Simple upload

For most use cases, use blob.handleUpload() in a route that receives FormData.

server/api/upload.post.ts
import { defineEventHandler } from 'h3'
import { blob } from '@vitehub/blob'

export default defineEventHandler(async (event) => {
  return await blob.handleUpload(event, {
    formKey: 'files',
    multiple: true,
    ensure: {
      maxSize: '10MB',
      types: ['image/jpeg', 'image/png', 'image/webp'],
    },
    put: {
      addRandomSuffix: true,
      prefix: 'images',
    },
  })
})

handleUpload()

blob.handleUpload() validates uploaded files and stores them with blob.put().

await blob.handleUpload(event, options)

Parameters

event
H3Event required
The current request event.
options
Object
Upload options.

Return value

Returns an array of BlobObject items. When multiple is false, the array contains a single uploaded blob.

Full example

server/api/upload.post.ts
import { defineEventHandler } from 'h3'
import { blob } from '@vitehub/blob'

export default defineEventHandler(async (event) => {
  return await blob.handleUpload(event, {
    formKey: 'files',
    multiple: true,
    ensure: {
      maxSize: '10MB',
      types: ['image'],
    },
    put: {
      addRandomSuffix: true,
      prefix: 'avatars',
    },
  })
})

Multipart upload

For large files, expose blob.handleMultipartUpload() on a dedicated route and drive the upload flow from your app or service code.

server/api/files/multipart/[action]/[...pathname].ts
import { defineEventHandler } from 'h3'
import { blob } from '@vitehub/blob'

export default defineEventHandler(async (event) => {
  return await blob.handleMultipartUpload(event, {
    addRandomSuffix: true,
    prefix: 'uploads',
  })
})

handleMultipartUpload()

blob.handleMultipartUpload() handles multipart upload lifecycle actions:

  • create
  • upload
  • complete
  • abort
await blob.handleMultipartUpload(event, options)

Parameters

event
H3Event required
The current request event.
options
Object
Multipart upload options.

Return value

The route returns the multipart payload for the current action:

  • create: { pathname, uploadId }
  • upload: { partNumber, etag }
  • complete: BlobObject
  • abort: no content

useMultipartUpload()

Use the client helper from @vitehub/blob/client to drive multipart uploads from a Nuxt app or any Vue client that can reach your Nitro routes.

app/pages/upload-large.vue
<script setup lang="ts">
import { useMultipartUpload } from '@vitehub/blob/client'

const uploader = useMultipartUpload('/api/files/multipart', {
  partSize: 10 * 1024 * 1024,
  concurrent: 3,
})

const file = ref<File | null>(null)
const progress = ref(0)
const uploading = ref(false)
let abortUpload: null | (() => Promise<void>) = null

async function startUpload() {
  if (!file.value)
    return

  uploading.value = true
  const { completed, progress: uploadProgress, abort } = uploader(file.value)
  abortUpload = abort

  watch(uploadProgress, (value) => {
    progress.value = value
  }, { immediate: true })

  try {
    await completed
  }
  finally {
    uploading.value = false
    abortUpload = null
  }
}
</script>

Parameters

baseURL
string required
The multipart route base URL.
options
Object

Return value

Returns a function that accepts a File and returns:

{
  completed: Promise<BlobObject | undefined>
  progress: Readonly<Ref<number>>
  abort: () => Promise<void>
}
When the public runtime config reports blobProvider: 'vercel-blob', useMultipartUpload() switches to the Vercel Blob client upload flow automatically.

Manual multipart flow

You can still use the lower-level blob SDK directly when you need custom upload behavior.

const multipart = await blob.createMultipartUpload('videos/demo.mp4', {
  contentType: 'video/mp4',
  prefix: 'videos',
})

await multipart.uploadPart(1, chunk1)
await multipart.uploadPart(2, chunk2)

await multipart.complete([
  { partNumber: 1, etag: 'etag-1' },
  { partNumber: 2, etag: 'etag-2' },
])