When you create a Vercel Blob store you pick Private or Public. You cannot change it later. Pick wrong and you migrate every file to a new store.
Private stores are generally available and need @vercel/blob 2.3 or newer.
The difference
| Private | Public | |
|---|---|---|
| Write | Authenticated | Authenticated |
| Read | Needs a credential | Anyone with the URL |
| URL host | <store>.private.blob.vercel-storage.com | <store>.public.blob.vercel-storage.com |
| Search indexing | Impossible | Possible |
Every put(), upload() and get() names the mode with access. It must
match the store.
Public: the pathname is the password
A public blob URL never expires. Anyone who gets it can read the file, forever, from anywhere. A random suffix makes URLs hard to guess. It does not make them secret once shared: logs, referrer headers, a screenshot, a support chat.
Right for: marketing images, public avatars, anything you would put on a CDN anyway.
Private: three ways to serve a file
Presigned GET URL. Your server calls
issueSignedToken()once, thenpresignUrl()per file with a shortvalidUntil. The browser reads straight from the store. The URL dies on schedule.import { issueSignedToken, presignUrl } from "@vercel/blob"; const token = await issueSignedToken({ pathname: "*", operations: ["get"] }); const { presignedUrl } = await presignUrl(token, { operation: "get", pathname: key, access: "private", validUntil: Date.now() + 15 * 60 * 1000, });issueSignedToken()is a network call;presignUrl()is a local HMAC. Cache the token on the server until near its expiry (it defaults to one hour, seven days at most) and sign many URLs with it. Keep the token'sclientSigningTokenon the server: anyone holding it can sign URLs.Stream through a route. Check auth,
get()the blob, return its stream. You control every header, includingContent-Dispositionfor a custom download name. You also pay transfer twice and hold a function open for the whole download. SetCache-Control: private, no-cache(orno-storefor sensitive data), and never cache the response on the CDN withs-maxage.BLOB_READ_WRITE_TOKENas a bearer header. For server-to-server only. It is a full read-write credential.
Which to pick
- Anything a user uploaded: private. A leaked link expires.
- Anything public by nature: public. Cheaper to serve, no signing.
- Both? Two stores. Hobby allows 100, Pro 500. There is no per-blob access setting to mix them.