@opensaas/stack-storage-vercel
v0.40.0
Published
Vercel Blob storage provider for OpenSaas Stack file uploads
Readme
@opensaas/stack-storage-vercel
Vercel Blob storage provider for OpenSaas Stack file uploads.
Installation
pnpm add @opensaas/stack-storage @opensaas/stack-storage-vercelUsage
// opensaas.config.ts
import { config, list } from '@opensaas/stack-core'
import { vercelBlobStorage } from '@opensaas/stack-storage-vercel'
import { image } from '@opensaas/stack-storage/fields'
export default config({
storage: {
avatars: vercelBlobStorage({
token: process.env.BLOB_READ_WRITE_TOKEN,
pathPrefix: 'avatars',
}),
},
lists: {
User: list({
fields: {
avatar: image({ storage: 'avatars' }),
},
}),
},
})Configuration Options
vercelBlobStorage({
// Optional - Authentication (see "Authentication" below)
token?: string // Static read-write token (or use BLOB_READ_WRITE_TOKEN env var)
storeId?: string // Blob store id for Vercel OIDC auth (or use BLOB_STORE_ID env var)
oidcToken?: string // Explicit OIDC token (or use VERCEL_OIDC_TOKEN env var)
// Optional - Storage options
pathPrefix?: string // Prefix for all files (e.g., 'avatars/')
generateUniqueFilenames?: boolean // Generate unique filenames (default: true)
allowOverwrite?: boolean // Allow overwriting an existing blob at the same pathname
// (default: true when generateUniqueFilenames is false, false otherwise)
public?: boolean // Make files publicly accessible (default: true)
cacheControl?: string // Cache control header (default: 'public, max-age=31536000, immutable')
})Overwriting existing files
The Vercel Blob API rejects uploads to an existing pathname unless allowOverwrite
is sent. With generateUniqueFilenames: false (stable filenames), the provider
defaults allowOverwrite to true since re-uploading to the same pathname is
the expected replace workflow (e.g. a field configured with cleanupOnReplace).
With generated filenames, collisions aren't expected, so it's left at the API's
default of rejecting overwrites. Set allowOverwrite explicitly to override
either default.
Authentication
Credentials are resolved by the @vercel/blob SDK on every call, in this order:
- An explicit
token(static read-write token) - Vercel OIDC: an OIDC token (
oidcTokenorVERCEL_OIDC_TOKEN) plus a store id (storeIdorBLOB_STORE_ID) - The
BLOB_READ_WRITE_TOKENenvironment variable
If none are available, the SDK throws a descriptive error on the first storage
operation. OIDC auth requires @vercel/blob 2.4.1 or later.
Setup
Option A: Static read-write token
Create a Vercel Blob store in your Vercel project dashboard
Get your Blob token from the Vercel dashboard
Add to environment variables:
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_...Option B: Vercel OIDC (no static token)
Create a Vercel Blob store in your Vercel project dashboard
Enable OIDC federation for your Vercel project
Provide the blob store id — either set the environment variable:
BLOB_STORE_ID=store_...or pass it in config:
avatars: vercelBlobStorage({
storeId: process.env.BLOB_STORE_ID,
pathPrefix: 'avatars',
})On Vercel, the deployment's VERCEL_OIDC_TOKEN is exchanged for blob
credentials automatically — no long-lived token to manage. For local
development, vercel env pull provides a short-lived OIDC token.
Examples
Basic Configuration
avatars: vercelBlobStorage({
pathPrefix: 'avatars',
})The token is automatically read from BLOB_READ_WRITE_TOKEN environment variable.
With Explicit Token
avatars: vercelBlobStorage({
token: process.env.BLOB_READ_WRITE_TOKEN,
pathPrefix: 'avatars',
public: true,
})Vercel OIDC (No Static Token)
avatars: vercelBlobStorage({
storeId: process.env.BLOB_STORE_ID,
pathPrefix: 'avatars',
})Private Files
documents: vercelBlobStorage({
pathPrefix: 'documents',
public: false, // Files are not publicly accessible
})Private blobs aren't fetchable via their plain URL — provider.download(filename) and provider.getSignedUrl(filename, expiresIn) read them through @vercel/blob's authorized read path instead, so serve them through a developer-controlled route (or a signed URL) rather than linking url/metadata.downloadUrl directly. See the storage docs for the serving pattern.
Custom Cache Control
images: vercelBlobStorage({
pathPrefix: 'images',
cacheControl: 'public, max-age=86400', // 1 day
})Features
Automatic CDN
Vercel Blob automatically distributes files via Vercel's global CDN for fast access worldwide.
Download URLs
Vercel Blob provides both regular and download URLs:
{
url: "https://blob.vercel-storage.com/...", // Direct URL
metadata: {
downloadUrl: "https://blob.vercel-storage.com/...", // Forces download
pathname: "avatars/filename.jpg"
}
}URL Stability
Vercel Blob URLs are stable and won't change once uploaded, making them safe to store in your database.
Environment Variables
One of the following authentication setups is required:
# Static token auth
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_...
# OR OIDC auth (VERCEL_OIDC_TOKEN is provided by Vercel automatically)
BLOB_STORE_ID=store_...Deployment
When deploying to Vercel:
- The
BLOB_READ_WRITE_TOKEN(static token auth) orVERCEL_OIDC_TOKEN(OIDC auth) is automatically available in the Vercel environment - No additional configuration needed
- Files are stored in Vercel's blob storage
- Global CDN distribution is automatic
Limits
Vercel Blob has different limits based on your plan:
- Hobby: 500 MB total storage
- Pro: Starts at 100 GB, pay-as-you-go
- Enterprise: Custom limits
Check Vercel's pricing page for current limits.
Local Development
For local development, you can still use Vercel Blob:
- Install Vercel CLI:
pnpm add -g vercel - Link your project:
vercel link - Pull environment variables:
vercel env pull - Your
BLOB_READ_WRITE_TOKENwill be available in.env.local
Alternatively, use a different storage provider for local development:
const storage =
process.env.NODE_ENV === 'production'
? {
avatars: vercelBlobStorage({ pathPrefix: 'avatars' }),
}
: {
avatars: localStorage({
uploadDir: './public/uploads',
serveUrl: '/uploads',
}),
}
export default config({
storage,
// ...
})License
MIT
