# Private vs public Vercel Blob stores: picking one you cannot change

> A Blob store's access mode is fixed at creation. Private needs a credential for every read; public is readable by anyone with the URL. How to serve each, and why user files belong in private.

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

1. **Presigned GET URL.** Your server calls `issueSignedToken()` once, then
   `presignUrl()` per file with a short `validUntil`. The browser reads straight
   from the store. The URL dies on schedule.

   ```ts
   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's
   `clientSigningToken` on the server: anyone holding it can sign URLs.

2. **Stream through a route.** Check auth, `get()` the blob, return its
   stream. You control every header, including `Content-Disposition` for a
   custom download name. You also pay transfer twice and hold a function open
   for the whole download. Set `Cache-Control: private, no-cache` (or
   `no-store` for sensitive data), and never cache the response on the CDN
   with `s-maxage`.

3. **`BLOB_READ_WRITE_TOKEN` as 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.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
