You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
storage-vercel-blob: clientUploads makes the item unique after PUT so the new media link gets 404s and the existing file for non postfixed version is overwritten #17823
If you upload the same filename twice, Payload creates a new media item (photo-1.jpg), but that original file uploaded never gets stored. The admin thumbnail looks fine. The main URL 404s. And the first photo.jpg in Blob gets overwritten, even though this is meant to be a new file.
What’s happening: with clientUploads: true, the browser PUTs photo.jpg first (allowOverwrite: true). Payload then uniquifies the new row to photo-1.jpg and only writes the sizes under that name. So photo-1-300x….jpg exists, photo-1.jpg does not, and the old photo.jpg blob is replaced.
It shouldn’t overwrite the old blob, and the new -1 row should have a file behind it. Local disk / server-side uploads already do this.
addRandomSuffix is not a workaround — it breaks image sizes (#9589).
Happy to open a 3.x PR that uniquifies the blob key before the client PUT
Confirming this reproduces identically on payload@3.90.2 / @payloadcms/storage-vercel-blob@3.90.2 with Vercel Blob on Postgres (not just Mongo) — same symptom (404 on the new doc's file, old doc's blob overwritten).
Root cause, traced through getClientUploadRoute.js / authorizeFileOverwrite.js: authorizeClientOverwrite() matches the raw, pre-dedup requested filename against existing docs' filename. On a match it returns allowOverwrite: true unconditionally — there's no signal in the handshake distinguishing "creating a new doc" from "intentionally replacing this doc's file". So the client PUTs to the old, un-keyed prefix/filename path, while generateFileData() independently dedupes the new doc's filename field to photo-1.jpg — a path that was never written. Confirmed via direct DB inspection: every doc with a filename collision has _objectKey: null; every doc with a unique filename gets a correct UUID _objectKey from resolveSignedURLKey().
We patched around it locally (pnpm patch) by forcing allowOverwrite to always be false in getClientUploadRoute.js, so every client upload goes through resolveSignedURLKey() and gets its own unique, _objectKey-keyed path. That's a blunt fix (it disables the "replace an existing doc's file via client upload" feature, which we don't use) — @zakriabilal's suggestion of uniquifying the blob key before the client PUT is the better general fix, since it'd preserve intentional-overwrite support. Happy to help test a PR against this.
Adding to the 3.90.2 report above: the overwrite can also hit a different, existing image when an editor replaces the file on another document.
On our staging site (storage-vercel-blob, clientUploads: true):
Image A had no _objectKey and was stored at staging/nature-pres-de-chez-vous.webp; it is used by published pages.
An editor replaced the file on image B with a file of the same name. authorizeClientOverwrite matched A, the only document at that prefix/filename, so the browser PUT replaced A's file.
So one save overwrote an image already in use and left the edited one empty.
The client-upload request only carries the collection, prefix, filename and MIME type, so the route cannot tell which document is being edited. The overwrite authorization also needs to know that document, and allow overwriting only its own key.
#17824 targets the rename half, but it re-uploads file.data, which is empty for client uploads since #17856, so on Vercel Blob it would store 0 bytes (#18333).
We switched to clientUploads: false until both are fixed.
Still reproduces on 3.93.0, and I can add one more case that loses data: replacing a doc's file with a file of the same name, on that same doc. The save returns HTTP 200 and the editor sees no error.
Doc before
Browser PUT goes to
Doc after
Result
Has _objectKey, stored at <key>/file.pdf
file.pdf
<key>/file-1.pdf
Doc URL 404. Old object deleted. New bytes orphaned at file.pdf.
No _objectKey, stored at file.pdf
file.pdf (overwrites it)
file-1.pdf
Doc URL 404. file.pdf is then deleted, so the old and the new file are both gone.
A re-save can't recover either doc. The new-doc case from the original report also still fails on 3.93.0.
Two details that may help with the fix:
getDocumentFileKeys in authorizeFileOverwrite.js builds keys from doc.prefix and ignores _objectKey. A keyed doc therefore "matches" prefix/filename, a path where its object isn't stored, so the upload is authorized to go there.
Nothing in payload/dist reads allowOverwrite. generateFileData still calls getSafeFileName, which matches the doc being replaced, so the rename happens even when overwriting the doc's own file. In the second row, the plugin's afterChange then sees different old and new keys and deletes the file that was just uploaded.
The workaround from the first comment here (force allowOverwrite to false in getClientUploadRoute.js) fixed all three cases in the same tests on 3.90.2. On a real Vercel Blob store in production (3.90.2), a new doc with a filename already in use got a working file-1.pdf. Replacing a file with one whose name was already taken gave a working file-2.pdf and deleted the previous object. The target code in getClientUploadRoute.js is unchanged in 3.93.0, so the same patch should still apply.
How I tested: the real admin UI in a browser against next build && next start, saving through the normal form. The adapter and plugin code ran for real, but the Vercel Blob API was a local stand-in, so the 3.93.0 results are not from a real store. Config: clientUploads: true, addRandomSuffix: true, disablePayloadAccessControl: true, on PDFs. I didn't test with addRandomSuffix: false, though these PDF uploads never received a suffix.
Activity
LukasHechenberger commentedon Sep 25, 2026
Confirming this reproduces identically on
payload@3.90.2/@payloadcms/storage-vercel-blob@3.90.2with Vercel Blob on Postgres (not just Mongo) — same symptom (404 on the new doc's file, old doc's blob overwritten).Root cause, traced through
getClientUploadRoute.js/authorizeFileOverwrite.js:authorizeClientOverwrite()matches the raw, pre-dedup requested filename against existing docs'filename. On a match it returnsallowOverwrite: trueunconditionally — there's no signal in the handshake distinguishing "creating a new doc" from "intentionally replacing this doc's file". So the client PUTs to the old, un-keyedprefix/filenamepath, whilegenerateFileData()independently dedupes the new doc'sfilenamefield tophoto-1.jpg— a path that was never written. Confirmed via direct DB inspection: every doc with a filename collision has_objectKey: null; every doc with a unique filename gets a correct UUID_objectKeyfromresolveSignedURLKey().We patched around it locally (
pnpm patch) by forcingallowOverwriteto always befalseingetClientUploadRoute.js, so every client upload goes throughresolveSignedURLKey()and gets its own unique,_objectKey-keyed path. That's a blunt fix (it disables the "replace an existing doc's file via client upload" feature, which we don't use) — @zakriabilal's suggestion of uniquifying the blob key before the client PUT is the better general fix, since it'd preserve intentional-overwrite support. Happy to help test a PR against this.franknoel commentedon Sep 26, 2026
Adding to the 3.90.2 report above: the overwrite can also hit a different, existing image when an editor replaces the file on another document.
On our staging site (
storage-vercel-blob,clientUploads: true):_objectKeyand was stored atstaging/nature-pres-de-chez-vous.webp; it is used by published pages.authorizeClientOverwritematched A, the only document at thatprefix/filename, so the browserPUTreplaced A's file.getSafeFileNamerenamed B tonature-pres-de-chez-vous-1.webp, a key the browser never uploaded to. Because it was a WebP, storage-vercel-blob: clientUploads stores WebP, GIF and cropped uploads as 0-byte objects #18333 then wrote a 0-byte object there.So one save overwrote an image already in use and left the edited one empty.
The client-upload request only carries the collection, prefix, filename and MIME type, so the route cannot tell which document is being edited. The overwrite authorization also needs to know that document, and allow overwriting only its own key.
#17824 targets the rename half, but it re-uploads
file.data, which is empty for client uploads since #17856, so on Vercel Blob it would store 0 bytes (#18333).We switched to
clientUploads: falseuntil both are fixed.refactor(media): Cloudflare R2 replaces Vercel Blob as the media origin
w3jerry commentedon Nov 7, 2026
Still reproduces on 3.93.0, and I can add one more case that loses data: replacing a doc's file with a file of the same name, on that same doc. The save returns HTTP 200 and the editor sees no error.
_objectKey, stored at<key>/file.pdffile.pdf<key>/file-1.pdffile.pdf._objectKey, stored atfile.pdffile.pdf(overwrites it)file-1.pdffile.pdfis then deleted, so the old and the new file are both gone.A re-save can't recover either doc. The new-doc case from the original report also still fails on 3.93.0.
Two details that may help with the fix:
getDocumentFileKeysinauthorizeFileOverwrite.jsbuilds keys fromdoc.prefixand ignores_objectKey. A keyed doc therefore "matches"prefix/filename, a path where its object isn't stored, so the upload is authorized to go there.payload/distreadsallowOverwrite.generateFileDatastill callsgetSafeFileName, which matches the doc being replaced, so the rename happens even when overwriting the doc's own file. In the second row, the plugin'safterChangethen sees different old and new keys and deletes the file that was just uploaded.The workaround from the first comment here (force
allowOverwritetofalseingetClientUploadRoute.js) fixed all three cases in the same tests on 3.90.2. On a real Vercel Blob store in production (3.90.2), a new doc with a filename already in use got a workingfile-1.pdf. Replacing a file with one whose name was already taken gave a workingfile-2.pdfand deleted the previous object. The target code ingetClientUploadRoute.jsis unchanged in 3.93.0, so the same patch should still apply.How I tested: the real admin UI in a browser against
next build && next start, saving through the normal form. The adapter and plugin code ran for real, but the Vercel Blob API was a local stand-in, so the 3.93.0 results are not from a real store. Config:clientUploads: true,addRandomSuffix: true,disablePayloadAccessControl: true, on PDFs. I didn't test withaddRandomSuffix: false, though these PDF uploads never received a suffix.