Using the Blob SDK

Store, read, list, validate, serve, and delete files with `@vitehub/blob`.

The Blob SDK works across Local storage, S3, Vercel Blob, and Cloudflare R2.

Importing the blob storage

Use @vitehub/blob for both runtime helpers and host-agnostic helpers such as ensureBlob():

import { ensureBlob } from '@vitehub/blob'
import { blob } from '@vitehub/blob'

Use @vitehub/blob/client only for browser-side multipart uploads:

import { useMultipartUpload } from '@vitehub/blob/client'

List blobs

server/api/files.get.ts
import { defineEventHandler } from 'h3'
import { blob } from '@vitehub/blob'

export default defineEventHandler(async () => {
  const { blobs } = await blob.list({ limit: 10 })
  return blobs
})

list()

Returns blob metadata without downloading file contents.

await blob.list(options)

Use prefix to scope results and folded: true to also receive virtual folders:

const { blobs } = await blob.list({ prefix: 'images/' })
const { blobs, folders } = await blob.list({ folded: true })

Use cursor to paginate until hasMore is false:

let cursor: string | undefined
let allBlobs: unknown[] = []

do {
  const result = await blob.list({ cursor })
  allBlobs = allBlobs.concat(result.blobs)
  cursor = result.cursor
} while (cursor)

Serve a blob

server/routes/files/[...pathname].get.ts
import { createError, defineEventHandler, getRouterParam } from 'h3'
import { blob } from '@vitehub/blob'

export default defineEventHandler(async (event) => {
  const pathname = getRouterParam(event, 'pathname')
  if (!pathname)
    throw createError({ statusCode: 404, statusMessage: 'Not Found' })

  return await blob.serve(event, pathname)
})

serve()

Sets Content-Type, Content-Length, and ETag headers and returns the blob body as a stream.

await blob.serve(event, 'images/photo.jpg')

If you serve user-generated content, make sure you tightly control which content types you allow into storage.

Get blob metadata

const metadata = await blob.head('images/photo.jpg')

Returns a BlobObject without reading the file body.

Get blob body

const file = await blob.get('documents/report.pdf')
const text = await file?.text()

get()

Returns a Blob or null when the object does not exist.

Upload a blob

server/api/files.post.ts
import { createError, defineEventHandler, readFormData } from 'h3'
import { ensureBlob } from '@vitehub/blob'
import { blob } from '@vitehub/blob'

export default defineEventHandler(async (event) => {
  const form = await readFormData(event)
  const file = form.get('file') as File | null
  if (!file)
    throw createError({ statusCode: 400, statusMessage: 'Missing file' })

  ensureBlob(file, {
    maxSize: '1MB',
    types: ['image'],
  })

  return await blob.put(file.name, file, {
    prefix: 'images',
  })
})
For higher-level upload routes and multipart handlers, see the Upload guide.

put()

await blob.put('images/photo.jpg', file, {
  access: 'public',
  addRandomSuffix: true,
  prefix: 'uploads',
  customMetadata: {
    userId: '123',
  },
})

You can also upload data fetched from another source:

const response = await fetch('https://example.com/image.png')
await blob.put('downloads/image.png', await response.blob())

Delete blobs

await blob.del('images/photo.jpg')
await blob.del(['images/one.jpg', 'images/two.jpg'])
await blob.delete('images/legacy-alias.jpg')

del()

Deletes one or more blobs by pathname.

Validate uploads

ensureBlob(file, {
  maxSize: '1MB',
  types: ['image', 'application/pdf'],
})

ensureBlob()

Throws when a file exceeds the maximum size or does not match the allowed content types.

The helper accepts grouped content types such as image, video, audio, pdf, and exact MIME types such as application/pdf.