Vercel Blob
Vercel Blob. Prefers auto-rotating Vercel OIDC (VERCEL_OIDC_TOKEN + BLOB_STORE_ID), falls back to BLOB_READ_WRITE_TOKEN, or pass credentials manually.
Installation
@vercel/blob is an optional peer dependency of files-sdk - install alongside the SDK so the adapter’s imports resolve at runtime.
npm install files-sdk @vercel/blobpnpm add files-sdk @vercel/blobyarn add files-sdk @vercel/blobbun add files-sdk @vercel/blobUsage
On Vercel, the adapter prefers Vercel’s OIDC authentication when VERCEL_OIDC_TOKEN and BLOB_STORE_ID are present (both are auto-injected when the Blob store is connected to the project). OIDC tokens rotate automatically, so they remove the risk that a long-lived secret leaks from the codebase or environment. Off Vercel - or if OIDC isn’t configured - the adapter falls back to BLOB_READ_WRITE_TOKEN. An explicit token option always wins.
import { Files } from "files-sdk";
import { vercelBlob } from "files-sdk/vercel-blob";
// On Vercel: VERCEL_OIDC_TOKEN + BLOB_STORE_ID are auto-injected when the
// Blob store is connected to the project (OIDC, recommended). Off Vercel,
// or as a fallback, BLOB_READ_WRITE_TOKEN is used.
const files = new Files({ adapter: vercelBlob() });
Pass oidcToken and storeId directly for runtimes that don’t expose process.env (Vite, etc.), or to bypass env detection entirely:
// Frameworks that don't load .env.local into process.env (Vite, etc.)
// need OIDC credentials passed explicitly.
const files = new Files({
adapter: vercelBlob({
oidcToken: loadOidcToken(),
storeId: loadStoreId(),
}),
});
downloadTimeoutMs bounds the public-URL fetches issued by download() and the lazy bodies returned from head()/list(). Defaults to 5 minutes; pass 0 to disable. A hung CDN response would otherwise leak a fetch that never resolves.
access selects public or private blobs and is fixed at construction. Default "public" matches the existing behavior. With access: "private", uploads use Vercel’s private mode and reads route through blob.get() with whichever credentials the adapter resolved (OIDC or read-write token) instead of a public URL fetch - there is no permanent public URL for private blobs, so url() throws. Need both? Use two adapters.
Options
token?string
Long-lived read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Takes priority over OIDC even when both are present (mirrors the upstream `@vercel/blob` resolution order). For code running on Vercel, prefer leaving this unset and using OIDC instead.
stringoidcToken?string
Vercel OIDC token. Defaults to `process.env.VERCEL_OIDC_TOKEN`, which Vercel populates automatically on every deployment when a Blob store is connected to the project. OIDC tokens are short-lived and auto-rotated, so they remove the risk that a long-lived `BLOB_READ_WRITE_TOKEN` leaks from your codebase or environment. To activate OIDC, **both** `oidcToken` and `storeId` must be available (option or env) and `token` must be unset — that matches the upstream SDK's resolution order. Pass `oidcToken` explicitly when your framework doesn't load `.env.local` into `process.env` automatically (Vite, etc.) — the adapter would otherwise silently fall back to the read-write token.
stringstoreId?string
Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. Accepted in either `store_<id>` or `<id>` form (mirrors the SDK). Independently powers the `url()` fast path: when a `storeId` is known (from option, env, or derived from a `vercel_blob_rw_<storeId>_…` token), public URLs are synthesized without a round trip if `addRandomSuffix: false`.
stringaccess?"public" | "private"
Whether blobs uploaded by this adapter are public or private. - `"public"` (default): blobs are uploaded with `access: "public"` and reachable via their CDN URL without authentication. `url()` returns a permanent public URL. - `"private"`: blobs are uploaded with `access: "private"`. They cannot be fetched by URL — `download()` and the lazy bodies returned from `head()` / `list()` instead route through `blob.get(key, { access: "private" })`, which uses whichever credentials the adapter resolved (read-write token or OIDC). `url()` throws because there is no permanent public URL for private blobs. The setting is fixed at construction so a single `Files` instance is unambiguously one or the other. If you need both, instantiate two adapters.
"public" | "private"addRandomSuffix?boolean
Add a random suffix to uploaded keys (Vercel default). When `false`, the resulting pathname matches the key 1:1, which keeps the API consistent with S3/R2 where callers expect to control the key. Defaults to `false`.
booleanallowOverwrite?boolean
Allow overwriting existing keys on upload. Defaults to `true` so that the "predictable keys" behavior (`addRandomSuffix: false`) actually works — Vercel rejects same-pathname uploads otherwise. **Trade-off:** with the defaults, an `upload(key, ...)` call silently clobbers any existing object at `key`. If keys are derived from untrusted input or your callers expect "create-only" semantics, set `allowOverwrite: false` and handle the resulting Conflict.
booleandownloadTimeoutMs?number
Timeout in milliseconds for public-URL fetches issued by `download()`, and by lazy bodies returned from `head()`/`list()`. A hung CDN response would otherwise leak a fetch that never resolves. Defaults to 300_000 (5 minutes). Pass `0` to disable the timeout (not recommended in server contexts — a stuck request will pin a connection until the runtime tears it down).
numberLimitations
User metadata isn’t supported by the underlying API, so passing a non-empty metadata throws rather than silently dropping it. cacheControl is supported (it maps to the blob’s cacheControlMaxAge).
Compatibility
Public access
| Method | Status | Notes |
|---|---|---|
upload |
✅ | |
download |
✅ | |
delete |
✅ | |
list |
✅ | |
search |
✅ | |
head |
✅ | |
exists |
✅ | |
copy |
✅ | |
url |
⚠️ | Returns the permanent CDN URL. expiresIn is silently ignored (no signing primitive); responseContentDisposition throws (no Content-Disposition override available). Use a different provider for buckets with untrusted user-uploaded content. |
signedUploadUrl |
❌ | No presigned upload primitive. Use handleUpload() from @vercel/blob/client for browser uploads. |
Private access
| Method | Status | Notes |
|---|---|---|
upload |
✅ | |
download |
✅ | |
delete |
✅ | |
list |
✅ | |
head |
✅ | |
exists |
✅ | |
copy |
✅ | |
url |
❌ | No URL primitive for private blobs - the underlying SDK requires an authenticated blob.get() call with the token. Use download() instead, or instantiate a second public-access adapter. |
signedUploadUrl |
❌ | No presigned upload primitive. Use handleUpload() from @vercel/blob/client for browser uploads. |