Skip to content

fix(db-postgres): restoring version does not clear upload/relationship fields when value is undefined #15976

@xxarupakaxx

Description

@xxarupakaxx

Describe the Bug

When restoring a published version that has no upload/relationship field value, the previously draft-saved upload/relationship data persists in the database and is not removed.

Root Cause: In packages/drizzle/src/transform/write/traverseFields.ts, the null check uses strict equality (=== null), which does not catch undefined. When a version is restored and the upload/relationship field was never set, the field value is undefined (not null), so the relationship row is never added to relationshipsToDelete and remains in the database.

Affected Code (2 locations)

Line ~551 (localized fields):

if (localeData === null) {  // ❌ misses undefined
    relationshipsToDelete.push({
        locale: localeKey,
        path: relationshipPath,
    })
    return
}

Line ~572 (non-localized fields):

if (fieldData === null || (Array.isArray(fieldData) && fieldData.length === 0)) {  // ❌ misses undefined
    relationshipsToDelete.push({ path: relationshipPath })
    return
}

Fix

Change === null to == null (loose equality) to also catch undefined:

// localized
if (localeData == null) { // ✅ catches both null and undefined

// non-localized  
if (fieldData == null || (Array.isArray(fieldData) && fieldData.length === 0)) { // ✅

Why undefined occurs

  1. transform/read/traverseFields.ts reads version data — if the upload relationship row doesn't exist in the _rels table, the property is not set on the result object (it becomes undefined, not null)
  2. restoreVersion passes this version data to the write transform
  3. The write transform's traverseFields receives undefined for the field
  4. === null fails → relationship row is not scheduled for deletion → stale data persists

Link to the code that reproduces this issue

This is a bug in the core library code at packages/drizzle/src/transform/write/traverseFields.ts. The issue can be reproduced with any Payload project using db-postgres (or any drizzle-based adapter) with versioned collections containing upload or relationship fields.

Reproduction Steps

  1. Create a collection with versions: { drafts: true } and an upload field (e.g., image)
  2. Create a document without setting the image → Publish it
  3. Edit the document → Set an image → Save as Draft
  4. Click "Revert to published" (restore the published version that has no image)
  5. Expected: The image field should be empty (matching the published version)
  6. Actual: The image from the draft still appears and is saved to the database

This also affects relationship fields, not just upload fields.

Which area(s) are affected?

  • db: postgres

Environment Info

  • Payload v2: @payloadcms/db-postgres@0.8.7 (confirmed affected)
  • Payload v3: packages/drizzle latest main branch (confirmed same code exists at lines ~551, ~572)
  • Node.js: v22
  • PostgreSQL: 15+

Additional Context

We discovered this bug in our production Payload v2 project and confirmed it also exists in v3's packages/drizzle/src/transform/write/traverseFields.ts. We have a working patch for v2 and are happy to submit a PR for v3 as well.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions