Skip to content

fix: prevent self-collision in getSafeFileName on update#16578

Open
EvilConsultant wants to merge 1 commit into
payloadcms:mainfrom
EvilConsultant:fix/getSafeFilename-self-collision-on-update
Open

fix: prevent self-collision in getSafeFileName on update#16578
EvilConsultant wants to merge 1 commit into
payloadcms:mainfrom
EvilConsultant:fix/getSafeFilename-self-collision-on-update

Conversation

@EvilConsultant
Copy link
Copy Markdown

@EvilConsultant EvilConsultant commented May 11, 2026

Problem

Ran into an issue when I was running some image migrations (pulling things out of Cloudinary into Payload).

When a media document is updated with a new file upload (PATCH with multipart body), getSafeFileName calls docWithFilenameExists without excluding the document being updated. The query finds the document's own existing filename and treats it as a collision — incrementing the filename with a -1 suffix.

Example: updating a media doc that already has foo.jpg causes Payload to write the new file as foo-1.jpg, silently breaking any references to the original URL.

I assume this isn't a feature? It caused me a lot of issues with cached names, integrations between Payload and Medusa needing to refresh thumbnails - basically anything that had the old name and all I did was patch an update to the image (not expecting a name change).

Root cause

docWithFilenameExists queries:

where: { filename: { equals: filename } }

On a create this is correct — any existing doc with that name is a real conflict. On an update the document being replaced already owns the filename, so the match is a false positive.

Fix

Add an optional docId parameter to both getSafeFileName and docWithFilenameExists. When present, docId is appended as id: { not_equals: docId } so the current document is excluded from the collision check.

generateFileData passes originalDoc.id as docId when operation === 'update', which covers the PATCH-with-file path that triggers the bug.

Changes

  • docWithFilenameExists.ts — accept and apply optional docId exclusion
  • getSafeFilename.ts — accept and thread docId through; update JSDoc
  • generateFileData.ts — pass docId on update operations
  • getSafeFilename.spec.ts — four new tests covering the self-collision fix

Reproduction

// 1. Create a media document — filename stored as foo.jpg
// 2. PATCH the same document with a new file named foo.jpg
// Expected: filename remains foo.jpg
// Actual (before fix): filename becomes foo-1.jpg

This is also reproducible by running a script that re-uploads each media document to trigger Sharp image size regeneration — every doc ends up with -1 appended after the first run.

When a media document is updated with a new file upload, getSafeFileName
called docWithFilenameExists without excluding the document being updated.
The query found the document's own existing filename and incorrectly treated
it as a collision, appending a spurious `-1` suffix (e.g. foo.jpg → foo-1.jpg).

Fix: add an optional `docId` parameter to both getSafeFileName and
docWithFilenameExists. When present, the document with that ID is excluded
from the WHERE clause so a document cannot collide with itself.

generateFileData now passes `originalDoc.id` as `docId` when operation is
`update`, which covers the PATCH-with-file case that triggered the bug.

Closes #<issue>
@EvilConsultant EvilConsultant changed the title fix(uploads): prevent self-collision in getSafeFileName on update fix: prevent self-collision in getSafeFileName on update May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant