Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/fix-blob-handle-upload-pathname.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@vercel/blob": patch
---

fix(blob): respect pathname returned by onBeforeGenerateToken in handleUpload

When using `handleUpload()`, the pathname returned from
`onBeforeGenerateToken()` is now used when generating the client token.
Previously, the SDK always used the original client-supplied pathname even when
the server returned an overridden pathname.
39 changes: 39 additions & 0 deletions packages/blob/src/client.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,45 @@ describe('client uploads', () => {
});
});

it('uses pathname returned by onBeforeGenerateToken in the generated client token', async () => {
const token =
'vercel_blob_rw_12345fakeStoreId_30FakeRandomCharacters12345678';

const jsonResponse = await handleUpload({
token,
request: {
headers: { 'x-vercel-signature': '123' },
} as unknown as IncomingMessage,
body: {
type: 'blob.generate-client-token',
payload: {
pathname: 'client-supplied.txt',
clientPayload: null,
multipart: false,
},
},
onBeforeGenerateToken: async () => {
return Promise.resolve({
pathname: 'server-controlled/final-name.txt',
callbackUrl: 'https://example.com',
});
},
onUploadCompleted: async () => {
await Promise.resolve();
},
});

expect(jsonResponse.type).toEqual('blob.generate-client-token');
expect(
getPayloadFromClientToken((jsonResponse as any).clientToken),
).toMatchObject({
pathname: 'server-controlled/final-name.txt',
onUploadCompleted: {
callbackUrl: 'https://example.com',
},
});
});

it('ignores client callbackUrl when server provides one', async () => {
const token =
'vercel_blob_rw_12345fakeStoreId_30FakeRandomCharacters12345678';
Expand Down
11 changes: 8 additions & 3 deletions packages/blob/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ export interface HandleUploadOptions {
* @param clientPayload - A string payload specified on the client when calling upload()
* @param multipart - A boolean specifying whether the file is a multipart upload
*
* @returns An object with configuration options for the client token including the optional callbackUrl
* @returns An object with configuration options for the client token including optional callbackUrl and pathname overrides
*/
onBeforeGenerateToken: (
pathname: string,
Expand All @@ -534,6 +534,7 @@ export interface HandleUploadOptions {
| 'allowOverwrite'
| 'cacheControlMaxAge'
| 'ifMatch'
| 'pathname'
> & { tokenPayload?: string | null; callbackUrl?: string }
>;

Expand Down Expand Up @@ -592,7 +593,11 @@ export async function handleUpload({
multipart,
);
const tokenPayload = payload.tokenPayload ?? clientPayload;
const { callbackUrl: providedCallbackUrl, ...tokenOptions } = payload;
const {
callbackUrl: providedCallbackUrl,
pathname: overriddenPathname,
...tokenOptions
} = payload;
Comment on lines +596 to +600
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

payload is typed as the return value of onBeforeGenerateToken, but HandleUploadOptions['onBeforeGenerateToken'] currently does not include a pathname field in its return type (see packages/blob/src/client.ts around the HandleUploadOptions definition). Destructuring pathname: overriddenPathname here (and returning { pathname: ... } in tests/user code) will fail TypeScript type-checking. Update the onBeforeGenerateToken return type to allow an optional pathname override (and ideally document it) so this change is type-safe.

Copilot uses AI. Check for mistakes.
let callbackUrl = providedCallbackUrl;

// If onUploadCompleted is provided but no callbackUrl was provided, try to infer it from environment
Expand All @@ -619,7 +624,7 @@ export async function handleUpload({
clientToken: await generateClientTokenFromReadWriteToken({
...tokenOptions,
token: resolvedToken,
pathname,
pathname: overriddenPathname ?? pathname,
onUploadCompleted: callbackUrl
? {
callbackUrl,
Expand Down