ViteHub is still experimental. Expect bugs and breaking changes.

Blob

Store uploads, generated files, binary objects, and metadata with one object-storage API.

Use Blob for uploads, generated media, PDFs, exports, and other objects that don't need a file tree.

Use Workspace when files need paths, snapshots, diffs, Source sync, or agent access. A Blob Store only keeps objects and their metadata.

Quick start

Install

Terminal
pnpm add @vite-hub/blob

Configure

vite.config.ts
import { hubBlob } from '@vite-hub/blob/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [hubBlob()],
})

Start using it

server/api/files.post.ts
import { blob } from '@vite-hub/blob'

export default defineEventHandler(async () => {
  const [error, object] = await blob.put('hello.txt', 'Hello from ViteHub')
  if (error) throw error
  return object
})

Public imports

ImportUse
blob from @vite-hub/blobRead and write the Default Blob Store or named Blob Stores.
detectContentType from vite-hub/blob/content-type or @vite-hub/blob/content-typeClassify common image and PDF signatures before storage.
ensureBlob from @vite-hub/blob or @vite-hub/blob/ensureValidate upload size and content type.
hubBlob from @vite-hub/blob/viteRegister Blob runtime configuration and Provider Output.
resolveBlobViteConfig from @vite-hub/blob/viteResolve Blob Vite runtime config manually.
@vite-hub/blob/drivers/*Import provider-specific Blob Driver Modules.

All Blob driver, object, list, put, store, and module types are exported from @vite-hub/blob.

Blob writes preserve the metadata you provide. detectContentType() checks common leading signatures, but it doesn't validate the complete file or prove that the file is safe.

Store configuration

Configure one default Blob Store directly, or configure named stores with blob.stores.

vite.config.ts
export default defineConfig({
  plugins: [hubBlob()],
  blob: {
    stores: {
      default: { driver: 'fs' },
      reports: { driver: 'vercel-blob', access: 'private' },
    },
  },
})
ShapeDescription
blob: falseDisables Blob runtime configuration.
blob: BlobStoreConfigConfigures one Default Blob Store with a driver and its provider options. Without a driver, ViteHub infers Cloudflare R2, Netlify Blobs, Vercel Blob, or local filesystem storage from the host and runtime env.
blob: { stores: Record<string, BlobStoreConfig> }Defines named Blob Stores. stores.default is required.
blob: { serve: false }Disables Blob route generation. This is the default.
blob: { serve: true }Generates an opt-in Nitro route at /api/_vitehub/blob/** for serving the Default Blob Store.
blob: { serve: { route?, store?, publicBaseUrl?, headers? } }Generates an opt-in Nitro route. route defaults to /api/_vitehub/blob, store defaults to default, publicBaseUrl changes generated public URLs, and headers adds static response headers.

Provider options

Every Blob Store config is a discriminated union selected by driver. Keep credentials in Server Env or provider-managed secrets; fields in these tables describe the exact public config shape, not a recommendation to commit secrets to Vite config.

Local filesystem

OptionTypeDefaultDescription
driver'fs'RequiredSelects local filesystem storage.
basestringBLOB_FS_BASE or .vitehub/data/blobSets the storage directory.
defaultUrlExpiresInnumberFiles SDK defaultSets the default generated URL lifetime in seconds.
urlBaseUrlstringNoneSets the base URL returned by the filesystem adapter.

Filesystem storage is for local or single-process use. It does not become durable shared storage on a serverless host.

Cloudflare R2

OptionTypeDefaultDescription
driver'cloudflare-r2'RequiredSelects the Cloudflare R2 driver.
bindingstringBLOBNames the runtime R2 binding.
bucketNamestringR2 or Blob bucket envNames the bucket for Provider Output and HTTP fallback.
accountIdstringCloudflare account envSupplies the account id for HTTP fallback.
accessKeyIdstringR2 access-key envSupplies the HTTP fallback access key.
secretAccessKeystringR2 secret-key envSupplies the HTTP fallback secret.
defaultUrlExpiresInnumberFiles SDK defaultSets the default signed URL lifetime in seconds.
publicBaseUrlstringNoneUses a public or CDN base URL for objects instead of signed URLs when supported.

The runtime binding takes precedence. HTTP credentials are used when no active binding exists.

Vercel Blob

OptionTypeDefaultDescription
driver'vercel-blob'RequiredSelects Vercel Blob.
access'private' | 'public''public'Sets the store-level access policy. A blob.put() call can override it.
allowOverwritebooleantrueAllows writes to replace an existing pathname.
downloadTimeoutMsnumberProvider defaultSets the download timeout in milliseconds.
tokenstringBLOB_READ_WRITE_TOKENSupplies the Vercel Blob token. ViteHub resolves masked build-time values again at runtime.

Netlify Blobs

OptionTypeDefaultDescription
driver'netlify-blobs'RequiredSelects Netlify Blobs.
namestringvitehub-blobNames the Netlify Blob Store.
consistency'eventual' | 'strong'Provider defaultSelects the Netlify read-consistency mode.
deployScopedbooleanProvider defaultScopes the store to the active deploy when enabled.
siteIDstringNetlify runtime envSupplies the Netlify site id outside an injected runtime.
tokenstringNetlify runtime envSupplies the Netlify access token outside an injected runtime.

S3 and S3-compatible providers

The s3, akamai, digitalocean-spaces, hetzner, storj, and minio drivers share object-storage routing options. Their required fields differ.

DriverRequired fieldsViteHub defaults
s3bucketNone
akamaibucket, regionNone
digitalocean-spacesbucket, regionNone
hetznerbucket, regionNone
storjbucketNone
minioNoneBucket vitehub-blob, endpoint http://localhost:9000, region us-east-1, and forcePathStyle: true
OptionDriversTypeDescription
driverAllprovider literalSelects one driver from the table above.
bucketAllstringNames the object-storage bucket. It is optional only for minio.
endpointAllstringOverrides the provider endpoint.
regionAllstringSelects the provider region. It is required for Akamai, DigitalOcean Spaces, and Hetzner.
forcePathStyleAllbooleanUses path-style bucket URLs instead of virtual-hosted URLs.
publicBaseUrlAllstringUses a public or CDN base URL for objects.
defaultUrlExpiresInAllnumberSets the default signed URL lifetime in seconds.
credentialss3{ accessKeyId, secretAccessKey, sessionToken? }Supplies an explicit AWS-compatible credential object.
accessKeyIdProvider-specific drivers and miniostringSupplies the access key directly.
secretAccessKeyProvider-specific drivers and miniostringSupplies the secret key directly.

MinIO resolves credentials from MINIO_ACCESS_KEY_ID, MINIO_ACCESS_KEY, MINIO_ROOT_USER, or AWS_ACCESS_KEY_ID, with matching secret-key env aliases. Other S3-compatible adapters also support their provider or SDK credential sources.

Google Cloud Storage

OptionTypeRequiredDescription
driver'gcs'YesSelects Google Cloud Storage.
bucketstringYesNames the bucket.
credentials{ client_email, private_key }NoSupplies service-account credentials inline.
keyFilenamestringNoLoads service-account credentials from a file.
projectIdstringNoSelects the Google Cloud project.
defaultUrlExpiresInnumberNoSets the default signed URL lifetime in seconds.
publicBaseUrlstringNoUses a public or CDN base URL for objects.

Azure Blob Storage

OptionTypeRequiredDescription
driver'azure'YesSelects Azure Blob Storage.
containerstringYesNames the container.
accountNamestringNoSupplies the storage account name.
accountKeystringNoAuthenticates with the account key.
connectionStringstringNoAuthenticates and configures the endpoint with an Azure connection string.
sasTokenstringNoAuthenticates with a shared access signature.
endpointstringNoOverrides the Blob service endpoint.
defaultUrlExpiresInnumberNoSets the default signed URL lifetime in seconds.
publicBaseUrlstringNoUses a public or CDN base URL for objects.

Supabase Storage

OptionTypeRequiredDescription
driver'supabase'YesSelects Supabase Storage.
bucketstringYesNames the bucket.
urlstringNoSupplies the Supabase project URL.
keystringNoSupplies the Supabase API key.
publicbooleanNoTreats the bucket as public.
publicBaseUrlstringNoOverrides the public object URL base.
defaultUrlExpiresInnumberNoSets the default signed URL lifetime in seconds.

UploadThing

OptionTypeRequiredDescription
driver'uploadthing'YesSelects UploadThing.
tokenstringNoSupplies the UploadThing token.
acl'private' | 'public-read'NoSets the uploaded object ACL.
regionstringNoSelects the upload region.
slugstringNoSelects the UploadThing route or file slug.
downloadTimeoutMsnumberNoSets the download timeout in milliseconds.
defaultUrlExpiresInnumberNoSets the default generated URL lifetime in seconds.

Google Drive

OptionTypeRequiredDescription
driver'google-drive'YesSelects Google Drive.
credentials{ client_email, private_key }NoSupplies service-account credentials inline.
keyFilenamestringNoLoads service-account credentials from a file.
subjectstringNoSelects the delegated Workspace user.
driveIdstringNoSelects a shared drive.
rootFolderIdstringNoRestricts objects to a root folder.
fileIdCacheSizenumberNoLimits the path-to-file-id cache.
publicByDefaultbooleanNoMakes newly written files public by default.

OneDrive

OptionTypeRequiredDescription
driver'onedrive'YesSelects OneDrive or SharePoint-backed storage.
accessTokenstring or async callbackNoSupplies or resolves a Microsoft Graph access token.
clientCredentials{ tenantId, clientId, clientSecret }NoUses the OAuth client-credentials flow.
oauth{ clientId, clientSecret, refreshToken, tenantId? }NoUses a refresh-token OAuth flow.
driveIdstringNoSelects a drive directly.
siteIdstringNoSelects a SharePoint site.
userIdstringNoSelects a user's drive.
rootFolderPathstringNoRestricts objects to a root folder path.
copyTimeoutMsnumberNoSets the asynchronous copy timeout in milliseconds.
publicByDefaultbooleanNoMakes newly written files public by default.

Dropbox

OptionTypeRequiredDescription
driver'dropbox'YesSelects Dropbox.
accessTokenstring or async callbackNoSupplies or resolves an access token.
appKeystringNoSupplies the OAuth app key.
appSecretstringNoSupplies the OAuth app secret.
refreshTokenstringNoRefreshes OAuth access with the app credentials.
rootFolderPathstringNoRestricts objects to a root folder path.
publicByDefaultbooleanNoCreates shared links by default.
publicBaseUrlstringNoUses an app-owned public URL base.
defaultUrlExpiresInnumberNoSets the default generated URL lifetime in seconds.

Box

OptionTypeRequiredDescription
driver'box'YesSelects Box.
developerTokenstringNoAuthenticates with a Box developer token.
ccg{ clientId, clientSecret, enterpriseId?, userId? }NoUses Box Client Credentials Grant authentication.
jwt{ configJsonString } | { configFilePath }NoUses a Box JWT application configuration.
oauth{ clientId, clientSecret, refreshToken }NoUses an OAuth refresh-token flow.
rootFolderIdstringNoRestricts objects to a root folder.
publicByDefaultbooleanNoCreates shared links by default.
publicBaseUrlstringNoUses an app-owned public URL base.
defaultUrlExpiresInnumberNoSets the default generated URL lifetime in seconds.

Use it at runtime

Use the blob Runtime Helper from server code.

server/api/files.post.ts
import { blob } from '@vite-hub/blob'

export default defineEventHandler(async (event) => {
  const body = await readBody<{ path: string, text: string }>(event)

  const [error] = await blob.put(body.path, body.text, {
    contentType: 'text/plain',
    customMetadata: { source: 'api' },
  })
  if (error) throw error

  return { ok: true }
})
server/api/files/[...path].get.ts
import { blob } from '@vite-hub/blob'

export default defineEventHandler(async (event) => {
  const path = getRouterParam(event, 'path')!
  const [error, object] = await blob.get(path)
  if (error) throw error

  if (!object) {
    throw createError({ statusCode: 404 })
  }

  return object
})

Use named Blob Stores when configuration defines multiple stores.

server/reports.ts
import { blob } from '@vite-hub/blob'

export const reports = blob.store('reports')

Serve blob-backed assets

Blob serving is opt-in. Set serve in the Blob config, or pass hubBlob({ serve: true }), to generate a Nitro route that serves Blob-backed assets through blob.serve().

vite.config.ts
export default defineConfig({
  plugins: [hubBlob()],
  blob: {
    driver: 'fs',
    serve: true,
  },
})

serve: true uses /api/_vitehub/blob as the route base. ViteHub chooses a namespaced API route by default so generated handlers avoid app routes, static assets, and framework asset directories. The default also mirrors server API route conventions.

Use an explicit serve.route for product-facing asset URLs.

vite.config.ts
export default defineConfig({
  plugins: [hubBlob()],
  blob: {
    driver: 's3',
    bucket: 'app-assets',
    serve: {
      route: '/assets',
      headers: {
        'Cache-Control': 'public, max-age=300',
        'X-Content-Type-Options': 'nosniff',
      },
    },
  },
})

Use serve.headers for static cache and security policy. Blob metadata remains authoritative for content headers such as Content-Type, Content-Length, and ETag.

The generated Nitro route maps ${route}/** to the selected Blob Store and delegates streaming to blob.store(storeName).serve(event, pathname). The default route is a safe framework default. It is not a recommendation that every app expose public assets under /api.

Objects from the served store include a URL. With serve.publicBaseUrl, the URL is absolute. Without it, the URL is route-relative so request-aware consumers can resolve it against their own origin.

Runtime helper

blob implements BlobStorage.

Every async method returns [error, value]. Expected provider and storage failures are ViteHubError values with BLOB_* codes, so application code can apply HTTP, retry, logging, or best-effort policy without try/catch. Invalid arguments, unknown stores, and unsupported signing capabilities still throw because they indicate API or configuration misuse. Generated serving routes unwrap blob.serve() and pass its error to H3.

MethodDescription
blob.put(pathname, body, options?)Stores text, bytes, streams, ArrayBuffers, or Blob objects.
blob.get(pathname)Reads a Blob or returns null.
blob.head(pathname)Reads object metadata.
blob.list(options?)Lists objects with optional prefix, limit, cursor, and folded folders.
blob.del(pathnames)Deletes one or more objects.
blob.sign(pathname, options)Signs a short-lived GET or PUT request for one object.
blob.serve(event, pathname)Serves an object stream through an H3 event.
blob.store(name)Selects a named Blob Store.

Write options

OptionTypeDescription
contentTypestringStored MIME type.
contentLengthstringExpected content length when the provider supports it.
customMetadataRecord<string, string>Provider custom metadata.
accessBlobPutOptions['access']Object access policy when the driver supports it. Values: private, public.
addRandomSuffixbooleanAdds a random suffix when supported by the driver.
prefixstringProvider path prefix when supported by the driver.

Signed requests

Use blob.sign() when a client or provider needs short-lived direct access to one private object. The result contains the URL, HTTP method, and every header that must be sent with the request.

server/api/uploads/presign.post.ts
import { blob } from '@vite-hub/blob'

const [sourceError, source] = await blob.sign('users/user/jobs/job/source.mp3', {
  method: 'GET',
  expiresIn: 6 * 60 * 60,
})
if (sourceError) throw sourceError

const [uploadError, upload] = await blob.sign('users/user/jobs/job/source.mp3', {
  method: 'PUT',
  expiresIn: 15 * 60,
  contentType: 'audio/mpeg',
  createOnly: true,
})
if (uploadError) throw uploadError

Send upload.headers unchanged with the PUT body. contentType binds the upload MIME type into the signed request. createOnly binds a provider condition that rejects the upload when the object already exists; drivers that cannot enforce it throw instead of silently allowing an overwrite.

Cloudflare R2 signs through its S3-compatible HTTP credentials, including when normal reads and writes use a Workers binding. A binding alone cannot mint a presigned URL, so configure accountId, accessKeyId, secretAccessKey, and bucketName through runtime environment values. R2 presigned URLs accept expiries from 1 second through 7 days, and the S3 compatibility contract supports If-None-Match on PutObject.

ensureBlob(blob, options)

Use ensureBlob() at upload boundaries.

OptionTypeDescription
maxSizeBlobSizeRejects blobs larger than the limit. Examples: 4MB, 128KB, 1GB.
typesBlobType[]Allows exact MIME types or broad types such as image, video, audio, pdf, and text.

Provider output

The Blob package selects the default or named store and loads its driver. Put provider bucket names, tokens, and bindings in integration configuration or deployment setup.

Application code keeps importing blob from @vite-hub/blob when you switch providers.

Connect Blob to Agents

Direct Blob access is for server code. To let a model inspect or edit scoped object storage, attach the Blob Capability.

Give a Blob Capability the narrowest useful key prefix and configure write access deliberately. Use Workspace when the model needs a file tree, diffs, snapshots, or Source-backed context.

Production checks

Store content types and metadata at write time. Avoid guessing object type later from path names.

Blob can store Workspace data, but it doesn't provide a file tree to an Agent. Workspace handles file operations, rules, snapshots, and diffs.

Blob stores binary objects and small object metadata. Keep catalogs, indexes, permissions, search records, domain records, and richer metadata queries in KV, Database, or another NoSQL/catalog store next to Blob.

Cloudflare R2 bucket

Cloudflare R2 Blob Stores use the configured runtime binding when it exists. binding defaults to BLOB, and bucketName lets ViteHub emit the matching Cloudflare R2 bucket binding in Provider Output.

vite.config.ts
export default defineConfig({
  blob: {
    driver: 'cloudflare-r2',
    binding: 'BLOB',
    bucketName: 'assets',
  },
})

When no runtime binding exists, ViteHub falls back to R2 HTTP access through files-sdk/r2. Set accessKeyId and secretAccessKey with runtime env, not vite.config.ts; non-secret values such as bucketName can stay in config.

.env
R2_ACCOUNT_ID=account-id
R2_ACCESS_KEY_ID=access-key-id
R2_SECRET_ACCESS_KEY=secret-access-key
R2_BUCKET_NAME=assets
Runtime valueSource
accountIdR2_ACCOUNT_ID, CLOUDFLARE_R2_ACCOUNT_ID, CLOUDFLARE_ACCOUNT_ID
accessKeyIdR2_ACCESS_KEY_ID, CLOUDFLARE_R2_ACCESS_KEY_ID
secretAccessKeyR2_SECRET_ACCESS_KEY, CLOUDFLARE_R2_SECRET_ACCESS_KEY
bucketNamebucketName config, or BLOB_BUCKET_NAME, CLOUDFLARE_R2_BUCKET_NAME, R2_BUCKET_NAME read at config/build time for generated Cloudflare r2_buckets. HTTP fallback can also read these names from active runtime env.

Install the optional R2 HTTP dependencies only when you rely on fallback access.

Terminal
pnpm add files-sdk @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner

S3-compatible object storage

Use driver: 's3' for production S3-compatible object storage that is not one of ViteHub's provider-specific drivers.

Terminal
pnpm add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
vite.config.ts
export default defineConfig({
  blob: {
    driver: 's3',
    bucket: 'app-assets',
    endpoint: process.env.S3_ENDPOINT,
    region: process.env.S3_REGION,
    publicBaseUrl: 'https://assets.example.com',
  },
})

Store S3 credentials in Server Env or the provider credential chain used by the S3 SDK. Put non-secret routing values such as bucket, endpoint, region, and publicBaseUrl in config.

Use Cloudflare R2 when the app runs with an R2 binding or R2 HTTP credentials. Use MinIO when local development or Docker Compose needs to exercise S3-compatible behavior.

MinIO object storage

Use MinIO when you want Docker Compose or local staging to exercise object-storage semantics instead of a mounted filesystem.

pnpm add files-sdk @aws-sdk/client-s3 @aws-sdk/s3-presigned-post @aws-sdk/s3-request-presigner
vite.config.ts
export default defineConfig({
  blob: {
    driver: 'minio',
  },
})
.env
MINIO_ENDPOINT=http://minio:9000
MINIO_ROOT_USER=minio
MINIO_ROOT_PASSWORD=password
BLOB_BUCKET_NAME=vitehub-blob

ViteHub reads MinIO credentials from runtime env and masks them in generated provider output. It accepts the Files SDK names MINIO_ACCESS_KEY_ID and MINIO_SECRET_ACCESS_KEY, plus Docker Compose aliases such as MINIO_ROOT_USER and MINIO_ROOT_PASSWORD. driver: 'minio' defaults to path-style S3 requests, us-east-1, http://localhost:9000, and the vitehub-blob bucket. For production Docker deployments, use managed s3 or a production S3-compatible store instead of a single-host Compose MinIO service.

Next steps