From 143aff57ae1047c9ef9ec21af8f3b83f4f61d62f Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 12 Jun 2025 05:57:37 -0700 Subject: [PATCH] fix: field inside an unnamed group field erroring when used as a title (#12771) Fixes https://github.com/payloadcms/payload/issues/12632 Config sanitisation will error without this PR when attempting to useAsTitle a field inside an unnamed group field. --- .../utilities/flattenTopLevelFields.spec.ts | 6 ++--- .../src/utilities/flattenTopLevelFields.ts | 9 ++++++-- .../admin/collections/UseAsTitleGroupField.ts | 22 +++++++++++++++++++ test/admin/config.ts | 2 ++ test/admin/slugs.ts | 2 ++ 5 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 test/admin/collections/UseAsTitleGroupField.ts diff --git a/packages/payload/src/utilities/flattenTopLevelFields.spec.ts b/packages/payload/src/utilities/flattenTopLevelFields.spec.ts index d9becf75a..5ff802773 100644 --- a/packages/payload/src/utilities/flattenTopLevelFields.spec.ts +++ b/packages/payload/src/utilities/flattenTopLevelFields.spec.ts @@ -152,7 +152,7 @@ describe('flattenFields', () => { // Should return the group as a top-level item, not the inner field expect(withoutExtract).toHaveLength(1) - expect(withoutExtract[0].type).toBe('group') + expect(withoutExtract[0].type).toBe('text') expect(withoutExtract[0].accessor).toBeUndefined() expect(withoutExtract[0].labelWithPrefix).toBeUndefined() }) @@ -635,8 +635,8 @@ describe('flattenFields', () => { const defaultResult = flattenTopLevelFields(unnamedTabWithUnnamedGroup) expect(defaultResult).toHaveLength(1) - expect(defaultResult[0].type).toBe('group') - expect(defaultResult[0].label).toBe('Unnamed Group In Tab') + expect(defaultResult[0].type).toBe('text') + expect(defaultResult[0].label).toBe('Nested In Unnamed Group') expect('accessor' in defaultResult[0]).toBe(false) expect('labelWithPrefix' in defaultResult[0]).toBe(false) diff --git a/packages/payload/src/utilities/flattenTopLevelFields.ts b/packages/payload/src/utilities/flattenTopLevelFields.ts index 71e24de53..5b0aa49d4 100644 --- a/packages/payload/src/utilities/flattenTopLevelFields.ts +++ b/packages/payload/src/utilities/flattenTopLevelFields.ts @@ -84,6 +84,7 @@ export function flattenTopLevelFields( } = normalizedOptions return fields.reduce[]>((acc, field) => { + // If a group field has subfields and has a name, otherwise we catch it below along with collapsible and row fields if (field.type === 'group' && 'fields' in field) { if (moveSubFieldsToTop) { const isNamedGroup = 'name' in field && typeof field.name === 'string' && !!field.name @@ -120,8 +121,12 @@ export function flattenTopLevelFields( }), ) } else { - // Hoisting diabled - keep as top level field - acc.push(field as FlattenedField) + if (fieldAffectsData(field)) { + // Hoisting diabled - keep as top level field + acc.push(field as FlattenedField) + } else { + acc.push(...flattenTopLevelFields(field.fields as TField[], options)) + } } } else if (field.type === 'tabs' && 'tabs' in field) { return [ diff --git a/test/admin/collections/UseAsTitleGroupField.ts b/test/admin/collections/UseAsTitleGroupField.ts new file mode 100644 index 000000000..c0e7fb326 --- /dev/null +++ b/test/admin/collections/UseAsTitleGroupField.ts @@ -0,0 +1,22 @@ +import type { CollectionConfig } from 'payload' + +import { useAsTitleGroupFieldSlug } from '../slugs.js' + +export const UseAsTitleGroupField: CollectionConfig = { + slug: useAsTitleGroupFieldSlug, + admin: { + useAsTitle: 'name', + }, + fields: [ + { + type: 'group', + label: 'unnamed group', + fields: [ + { + name: 'name', + type: 'text', + }, + ], + }, + ], +} diff --git a/test/admin/config.ts b/test/admin/config.ts index 91525be38..15702890e 100644 --- a/test/admin/config.ts +++ b/test/admin/config.ts @@ -23,6 +23,7 @@ import { Placeholder } from './collections/Placeholder.js' import { Posts } from './collections/Posts.js' import { UploadCollection } from './collections/Upload.js' import { UploadTwoCollection } from './collections/UploadTwo.js' +import { UseAsTitleGroupField } from './collections/UseAsTitleGroupField.js' import { Users } from './collections/Users.js' import { with300Documents } from './collections/With300Documents.js' import { CustomGlobalViews1 } from './globals/CustomViews1.js' @@ -174,6 +175,7 @@ export default buildConfigWithDefaults({ with300Documents, ListDrawer, Placeholder, + UseAsTitleGroupField, ], globals: [ GlobalHidden, diff --git a/test/admin/slugs.ts b/test/admin/slugs.ts index 2fb425c27..e987283ff 100644 --- a/test/admin/slugs.ts +++ b/test/admin/slugs.ts @@ -8,6 +8,8 @@ export const group1Collection1Slug = 'group-one-collection-ones' export const group1Collection2Slug = 'group-one-collection-twos' export const group2Collection1Slug = 'group-two-collection-ones' export const group2Collection2Slug = 'group-two-collection-twos' + +export const useAsTitleGroupFieldSlug = 'use-as-title-group-field' export const hiddenCollectionSlug = 'hidden-collection' export const notInViewCollectionSlug = 'not-in-view-collection' export const noApiViewCollectionSlug = 'collection-no-api-view'