Skip to content

Commit 112e081

Browse files
authored
fix(ui): ensure file field is only serialized at top-level for upload-enabled collections (#12074)
This fixes an issue where fields with the name `file` was being serialized as a top-level field in multipart form data even when the collection was not upload-enabled. This caused the value of `file` (when used as a regular field like a text, array, etc.) to be stripped from the `_payload`. - Updated `createFormData` to only delete `data.file` and serialize it at the top level if `docConfig.upload` is defined. - This prevents unintended loss of `file` field values for non-upload collections. The `file` field now remains safely nested in `_payload` unless it's part of an upload-enabled collection.
1 parent eab9770 commit 112e081

File tree

4 files changed

+49
-25
lines changed

4 files changed

+49
-25
lines changed

packages/ui/src/forms/Form/index.tsx

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ import { initContextState } from './initContextState.js'
5656
const baseClass = 'form'
5757

5858
export const Form: React.FC<FormProps> = (props) => {
59-
const { id, collectionSlug, docPermissions, getDocPreferences, globalSlug } = useDocumentInfo()
59+
const { id, collectionSlug, docConfig, docPermissions, getDocPreferences, globalSlug } =
60+
useDocumentInfo()
6061

6162
const {
6263
action,
@@ -491,8 +492,28 @@ export const Form: React.FC<FormProps> = (props) => {
491492

492493
let file = data?.file
493494

494-
if (file) {
495+
if (docConfig && 'upload' in docConfig && docConfig.upload && file) {
495496
delete data.file
497+
498+
const handler = getUploadHandler({ collectionSlug })
499+
500+
if (typeof handler === 'function') {
501+
let filename = file.name
502+
const clientUploadContext = await handler({
503+
file,
504+
updateFilename: (value) => {
505+
filename = value
506+
},
507+
})
508+
509+
file = JSON.stringify({
510+
clientUploadContext,
511+
collectionSlug,
512+
filename,
513+
mimeType: file.type,
514+
size: file.size,
515+
})
516+
}
496517
}
497518

498519
if (mergeOverrideData) {
@@ -504,37 +525,23 @@ export const Form: React.FC<FormProps> = (props) => {
504525
data = overrides
505526
}
506527

507-
const handler = getUploadHandler({ collectionSlug })
508-
509-
if (file && typeof handler === 'function') {
510-
let filename = file.name
511-
const clientUploadContext = await handler({
512-
file,
513-
updateFilename: (value) => {
514-
filename = value
515-
},
516-
})
517-
518-
file = JSON.stringify({
519-
clientUploadContext,
520-
collectionSlug,
521-
filename,
522-
mimeType: file.type,
523-
size: file.size,
524-
})
528+
const dataToSerialize: Record<string, unknown> = {
529+
_payload: JSON.stringify(data),
525530
}
526531

527-
const dataToSerialize = {
528-
_payload: JSON.stringify(data),
529-
file,
532+
if (docConfig && 'upload' in docConfig && docConfig.upload && file) {
533+
dataToSerialize.file = file
530534
}
531535

532536
// nullAsUndefineds is important to allow uploads and relationship fields to clear themselves
533-
const formData = serialize(dataToSerialize, { indices: true, nullsAsUndefineds: false })
537+
const formData = serialize(dataToSerialize, {
538+
indices: true,
539+
nullsAsUndefineds: false,
540+
})
534541

535542
return formData
536543
},
537-
[collectionSlug, getUploadHandler],
544+
[collectionSlug, docConfig, getUploadHandler],
538545
)
539546

540547
const reset = useCallback(

test/admin/collections/Posts.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ export const Posts: CollectionConfig = {
236236
},
237237
],
238238
},
239+
{
240+
name: 'file',
241+
type: 'text',
242+
},
239243
],
240244
labels: {
241245
plural: slugPluralLabel,

test/admin/e2e/document-view/e2e.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,17 @@ describe('Document View', () => {
512512
await expect(publishButton).toContainText('Publish in English')
513513
})
514514
})
515+
516+
describe('reserved field names', () => {
517+
test('should allow creation of field named file in non-upload enabled collection', async () => {
518+
await page.goto(postsUrl.create)
519+
const fileField = page.locator('#field-file')
520+
await fileField.fill('some file text')
521+
await saveDocAndAssert(page)
522+
523+
await expect(fileField).toHaveValue('some file text')
524+
})
525+
})
515526
})
516527

517528
async function createPost(overrides?: Partial<Post>): Promise<Post> {

test/admin/payload-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ export interface Post {
247247
sidebarField?: string | null;
248248
wavelengths?: ('fm' | 'am') | null;
249249
selectField?: ('option1' | 'option2')[] | null;
250+
file?: string | null;
250251
updatedAt: string;
251252
createdAt: string;
252253
_status?: ('draft' | 'published') | null;
@@ -665,6 +666,7 @@ export interface PostsSelect<T extends boolean = true> {
665666
sidebarField?: T;
666667
wavelengths?: T;
667668
selectField?: T;
669+
file?: T;
668670
updatedAt?: T;
669671
createdAt?: T;
670672
_status?: T;

0 commit comments

Comments
 (0)