Using the Blob SDK
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
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
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')
head()
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
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',
})
})
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.