@vercel/blob: client uploads can't set `access` — `onBeforeGenerateToken` omits it, so client uploads are locked to public
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 595
- Forks
- 101
- Avg merge
- 2h 24m
- Merged PRs (30d)
- 1
Description
Summary
There is no way to request private access for a client upload. handleUpload's onBeforeGenerateToken callback — the only server-side hook where a client-upload token is minted — cannot return access, so every client-uploaded blob is forced to public, even though private Blob shipped (#816).
This is a gap left over from the private-storage rollout: server put({ access: 'private' }) works, but the client-upload path (upload() → handleUpload() → generateClientTokenFromReadWriteToken()) was never given an access knob.
Where it's blocked (in packages/blob/src/client.ts)
onBeforeGenerateToken's return type is a Pick that omits access:
onBeforeGenerateToken: (
pathname: string,
clientPayload: string | null,
multipart: boolean,
) => Promise<
Pick<
GenerateClientTokenOptions,
| 'allowedContentTypes'
| 'maximumSizeInBytes'
| 'validUntil'
| 'addRandomSuffix'
| 'allowOverwrite'
| 'cacheControlMaxAge'
| 'ifMatch'
> & { tokenPayload?: string | null; callbackUrl?: string }
>;
And GenerateClientTokenOptions has no access field to Pick from — it extends BlobCommandOptions (token/oidcToken/storeId/abortSignal) and BlobClientTokenConstraintOptions (size/content-types/validUntil/suffix/overwrite/cache/ifMatch). access lives only on CommonCreateBlobOptions (packages/blob/src/helpers.ts), which the token options do not extend.
Net effect: returning { access: 'private', ... } from onBeforeGenerateToken is a TypeScript error, and there is no runtime channel to carry the intended access level into the signed client token.
Reproduction
// app/api/upload/route.ts
import { handleUpload } from '@vercel/blob/client';
export async function POST(req: Request) {
return Response.json(
await handleUpload({
request: req,
body: await req.json(),
onBeforeGenerateToken: async () => ({
access: 'private', // ❌ TS2353: 'access' does not exist in type Pick<GenerateClientTokenOptions, ...>
allowedContentTypes: ['application/pdf'],
}),
}),
);
}
Confirmed on @vercel/blob@2.5.0 (and 2.3.3) — the Pick and GenerateClientTokenOptions shape are identical in both.
Expected
Symmetry with the server-side API: a client upload should be able to be minted as private, e.g. onBeforeGenerateToken returning { access: 'private' }, so the resulting blob is created with private access.
Proposed fix (SDK side)
- Add an optional
access?: BlobAccessTypeto the client-token options (GenerateClientTokenOptions). - Add
'access'to thePickinonBeforeGenerateToken's return type.
Because generateClientTokenFromReadWriteToken already serializes all of its non-token args into the signed token payload, and handleUpload already spreads the onBeforeGenerateToken result into it, access would flow end-to-end into the token payload with no other runtime change. A PR implementing exactly this (with a unit test asserting the decoded token payload carries access, plus a changeset) is open — see below.
Open question for maintainers
Does the control-plane / api-storage side already honor an access field embedded in a vercel_blob_client_* token payload, or is additional backend work required for it to take effect? The SDK change is safe and minimal, but if the backend ignores token-level access, the type change alone would let developers express private client uploads without them actually being private — so I've flagged this explicitly rather than assume. Happy to adjust the PR (e.g. gate it, or wire it differently) based on how the backend consumes the token.
Context
- Follow-up to #816 (private Blob storage) — that issue delivered private access for server-side operations but did not cover the client-upload token path.
- Real-world motivation: uploading receipts/documents that contain PII/PHI directly from the browser; these should be private, but client uploads currently can only be public.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/blob/src/client.ts and packages/blob/src/helpers.ts, then trace generateClientTokenFromReadWriteToken and handleUpload. Check how client-token options are typed and how their decoded payload is tested. Done means access can be returned by onBeforeGenerateToken and is present in the signed payload, with confirmation that the backend honors it.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100