fix: skip validation when trashing documents with empty required fields (#13807)

### What?

Skip field validation when trashing documents with empty required
fields.

### Why?

When trashing a document that was saved as a draft with empty required
fields, Payload would run full validation and fail with "The following
fields are invalid" errors. This happened because trash operations were
treated as regular updates that require full field validation, even
though trashing is just a metadata change (setting `deletedAt`) and
shouldn't be blocked by content validation issues.
   
### How?

- Modified `skipValidation` logic in `updateDocument()` to skip
validation when `deletedAt` is being set in the update data

Fixes #13706
This commit is contained in:
Patrik
2025-09-16 10:09:39 -04:00
committed by GitHub
parent 3acdbf6b25
commit 3b13867aee
4 changed files with 44 additions and 4 deletions

View File

@@ -237,9 +237,11 @@ export const updateDocument = async <
overrideAccess, overrideAccess,
req, req,
skipValidation: skipValidation:
shouldSaveDraft && (shouldSaveDraft &&
collectionConfig.versions.drafts && collectionConfig.versions.drafts &&
!collectionConfig.versions.drafts.validate, !collectionConfig.versions.drafts.validate) ||
// Skip validation for trash operations since they're just metadata updates
Boolean(data?.deletedAt),
} }
if (publishSpecificLocale) { if (publishSpecificLocale) {

View File

@@ -12,6 +12,7 @@ export const Posts: CollectionConfig = {
{ {
name: 'title', name: 'title',
type: 'text', type: 'text',
required: true,
}, },
], ],
versions: { versions: {

View File

@@ -648,6 +648,43 @@ describe('trash', () => {
}) })
}) })
describe('trashing documents with validation issues', () => {
it('should allow trashing documents with empty required fields (draft scenario)', async () => {
// Create a draft document with empty required field
const draftDoc = await payload.create({
collection: postsSlug,
data: {
title: '', // Empty required field
_status: 'draft',
},
draft: true,
})
expect(draftDoc.title).toBe('')
expect(draftDoc._status).toBe('draft')
// Should be able to trash the document even with empty required field
const trashedDoc = await payload.update({
collection: postsSlug,
id: draftDoc.id,
data: {
deletedAt: new Date().toISOString(),
},
})
expect(trashedDoc.deletedAt).toBeDefined()
expect(trashedDoc.title).toBe('') // Title should still be empty
expect(trashedDoc._status).toBe('draft')
// Clean up
await payload.delete({
collection: postsSlug,
id: draftDoc.id,
trash: true,
})
})
})
describe('deleteByID operation', () => { describe('deleteByID operation', () => {
it('should throw NotFound error when trying to delete a soft-deleted document w/o trash: true', async () => { it('should throw NotFound error when trying to delete a soft-deleted document w/o trash: true', async () => {
await expect( await expect(

View File

@@ -134,7 +134,7 @@ export interface Page {
*/ */
export interface Post { export interface Post {
id: string; id: string;
title?: string | null; title: string;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
deletedAt?: string | null; deletedAt?: string | null;