From 8cfc6df19f6f451d26ec9519f3ab4e7d3e0fb785 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 27 Mar 2026 06:55:05 -0600 Subject: [PATCH 1/2] feat(storage-vercel-blob): add allowOverwrite option Adds a new `allowOverwrite` option to `VercelBlobStorageOptions` that is passed through to all `@vercel/blob` `put()` calls, including both server-side uploads and client upload token generation. When a client upload fails partway through (e.g., the blob is created but Payload's server-side processing returns a 400), an orphan blob is left behind. Subsequent uploads of the same file then fail with: "Vercel Blob: This blob already exists" Setting `allowOverwrite: true` prevents this error by silently overwriting the existing blob instead of rejecting the upload. Refs: #14709 --- .../src/getClientUploadRoute.ts | 4 +++- .../storage-vercel-blob/src/handleUpload.ts | 2 ++ packages/storage-vercel-blob/src/index.ts | 20 ++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/storage-vercel-blob/src/getClientUploadRoute.ts b/packages/storage-vercel-blob/src/getClientUploadRoute.ts index 0dc2d6434f6..d17b8202a51 100644 --- a/packages/storage-vercel-blob/src/getClientUploadRoute.ts +++ b/packages/storage-vercel-blob/src/getClientUploadRoute.ts @@ -9,6 +9,7 @@ type Args = { req: PayloadRequest }) => boolean | Promise addRandomSuffix?: boolean + allowOverwrite?: boolean cacheControlMaxAge?: number token: string } @@ -16,7 +17,7 @@ type Args = { const defaultAccess: Args['access'] = ({ req }) => !!req.user export const getClientUploadRoute = - ({ access = defaultAccess, addRandomSuffix, cacheControlMaxAge, token }: Args): PayloadHandler => + ({ access = defaultAccess, addRandomSuffix, allowOverwrite, cacheControlMaxAge, token }: Args): PayloadHandler => async (req) => { const body = (await req.json!()) as HandleUploadBody @@ -34,6 +35,7 @@ export const getClientUploadRoute = return Promise.resolve({ addRandomSuffix, + allowOverwrite, cacheControlMaxAge, }) }, diff --git a/packages/storage-vercel-blob/src/handleUpload.ts b/packages/storage-vercel-blob/src/handleUpload.ts index 4bf4f53f2ef..8be2366a40f 100644 --- a/packages/storage-vercel-blob/src/handleUpload.ts +++ b/packages/storage-vercel-blob/src/handleUpload.ts @@ -13,6 +13,7 @@ type HandleUploadArgs = { export const getHandleUpload = ({ access = 'public', addRandomSuffix, + allowOverwrite, baseUrl, cacheControlMaxAge, prefix = '', @@ -24,6 +25,7 @@ export const getHandleUpload = ({ const result = await put(fileKey, buffer, { access, addRandomSuffix, + allowOverwrite, cacheControlMaxAge, contentType: mimeType, token, diff --git a/packages/storage-vercel-blob/src/index.ts b/packages/storage-vercel-blob/src/index.ts index 65f83d8fd4d..71cd69b561a 100644 --- a/packages/storage-vercel-blob/src/index.ts +++ b/packages/storage-vercel-blob/src/index.ts @@ -34,6 +34,21 @@ export type VercelBlobStorageOptions = { */ addRandomSuffix?: boolean + /** + * Allow overwriting existing blobs with the same pathname. + * + * When `false` (the default), uploading a file with the same name as an + * existing blob will throw an error. This commonly happens when a previous + * upload fails partway through (the blob is created but the Payload record + * is not), leaving an orphan blob that blocks subsequent uploads of the + * same file. + * + * Set to `true` to silently overwrite the existing blob instead of erroring. + * + * @default false + */ + allowOverwrite?: boolean + /** * When enabled, fields (like the prefix field) will always be inserted into * the collection schema regardless of whether the plugin is enabled. This @@ -82,6 +97,7 @@ export type VercelBlobStorageOptions = { const defaultUploadOptions: Partial = { access: 'public', addRandomSuffix: false, + allowOverwrite: false, cacheControlMaxAge: 60 * 60 * 24 * 365, // 1 year enabled: true, } @@ -130,6 +146,7 @@ export const vercelBlobStorage: VercelBlobStoragePlugin = access: typeof options.clientUploads === 'object' ? options.clientUploads.access : undefined, addRandomSuffix: optionsWithDefaults.addRandomSuffix, + allowOverwrite: optionsWithDefaults.allowOverwrite, cacheControlMaxAge: options.cacheControlMaxAge, token: options.token ?? '', }), @@ -185,7 +202,7 @@ function vercelBlobStorageInternal( options: { baseUrl: string } & VercelBlobStorageOptions, ): Adapter { return ({ collection, prefix }): GeneratedAdapter => { - const { access, addRandomSuffix, baseUrl, cacheControlMaxAge, clientUploads, token } = options + const { access, addRandomSuffix, allowOverwrite, baseUrl, cacheControlMaxAge, clientUploads, token } = options if (!token) { throw new Error('Vercel Blob storage token is required') @@ -199,6 +216,7 @@ function vercelBlobStorageInternal( handleUpload: getHandleUpload({ access, addRandomSuffix, + allowOverwrite, baseUrl, cacheControlMaxAge, prefix, From 182b6fab124bd8f35b8235a353c502a71dea3161 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Thu, 2 Apr 2026 17:07:29 -0600 Subject: [PATCH 2/2] fix: pass allowOverwrite through to client handler Adds allowOverwrite to extraClientHandlerProps and the VercelBlobClientUploadHandler type/destructuring for consistency with addRandomSuffix. --- .../src/client/VercelBlobClientUploadHandler.ts | 3 ++- packages/storage-vercel-blob/src/index.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/storage-vercel-blob/src/client/VercelBlobClientUploadHandler.ts b/packages/storage-vercel-blob/src/client/VercelBlobClientUploadHandler.ts index 002939e1dfe..3740d3a27de 100644 --- a/packages/storage-vercel-blob/src/client/VercelBlobClientUploadHandler.ts +++ b/packages/storage-vercel-blob/src/client/VercelBlobClientUploadHandler.ts @@ -5,6 +5,7 @@ import { formatAdminURL } from 'payload/shared' export type VercelBlobClientUploadHandlerExtra = { addRandomSuffix: boolean + allowOverwrite: boolean baseURL: string prefix: string } @@ -14,7 +15,7 @@ export const VercelBlobClientUploadHandler = handler: async ({ apiRoute, collectionSlug, - extra: { addRandomSuffix, baseURL, prefix = '' }, + extra: { addRandomSuffix, allowOverwrite, baseURL, prefix = '' }, file, serverHandlerPath, serverURL, diff --git a/packages/storage-vercel-blob/src/index.ts b/packages/storage-vercel-blob/src/index.ts index 71cd69b561a..bce58ec5484 100644 --- a/packages/storage-vercel-blob/src/index.ts +++ b/packages/storage-vercel-blob/src/index.ts @@ -138,6 +138,7 @@ export const vercelBlobStorage: VercelBlobStoragePlugin = enabled: !isPluginDisabled && Boolean(options.clientUploads), extraClientHandlerProps: (collection) => ({ addRandomSuffix: !!optionsWithDefaults.addRandomSuffix, + allowOverwrite: !!optionsWithDefaults.allowOverwrite, baseURL: baseUrl, prefix: (typeof collection === 'object' && collection.prefix && `${collection.prefix}/`) || '',