diff --git a/docs/fields/radio.mdx b/docs/fields/radio.mdx index 6ec67992d6..4ddfbb29f0 100644 --- a/docs/fields/radio.mdx +++ b/docs/fields/radio.mdx @@ -50,6 +50,7 @@ export const MyRadioField: Field = { | **`admin`** | Admin-specific configuration. [More details](#admin-options). | | **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | +| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | | **`typescriptSchema`** | Override field type generation with providing a JSON schema | | **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | diff --git a/docs/fields/select.mdx b/docs/fields/select.mdx index 06c8836bce..7495ead495 100644 --- a/docs/fields/select.mdx +++ b/docs/fields/select.mdx @@ -53,6 +53,7 @@ export const MySelectField: Field = { | **`custom`** | Extension point for adding custom data (e.g. for plugins) | | **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | | **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. | +| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). | | **`typescriptSchema`** | Override field type generation with providing a JSON schema | | **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) | diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index 9d4ad6838c..af914d1b19 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -1045,6 +1045,13 @@ export type SelectField = { */ enumName?: DBIdentifierName hasMany?: boolean + /** Customize generated GraphQL and Typescript schema names. + * By default, it is bound to the collection. + * + * This is useful if you would like to generate a top level type to share amongst collections/fields. + * **Note**: Top level types can collide, ensure they are unique amongst collections, arrays, groups, blocks, tabs. + */ + interfaceName?: string options: Option[] type: 'select' } & ( @@ -1062,7 +1069,7 @@ export type SelectField = { export type SelectFieldClient = { admin?: AdminClient & Pick } & FieldBaseClient & - Pick + Pick type SharedRelationshipProperties = { filterOptions?: FilterOptions @@ -1269,6 +1276,13 @@ export type RadioField = { * Customize the DB enum name */ enumName?: DBIdentifierName + /** Customize generated GraphQL and Typescript schema names. + * By default, it is bound to the collection. + * + * This is useful if you would like to generate a top level type to share amongst collections/fields. + * **Note**: Top level types can collide, ensure they are unique amongst collections, arrays, groups, blocks, tabs. + */ + interfaceName?: string options: Option[] type: 'radio' validate?: RadioFieldValidation @@ -1277,7 +1291,7 @@ export type RadioField = { export type RadioFieldClient = { admin?: AdminClient & Pick } & FieldBaseClient & - Pick + Pick type BlockFields = { [key: string]: any diff --git a/packages/payload/src/utilities/configToJSONSchema.ts b/packages/payload/src/utilities/configToJSONSchema.ts index c363a3f903..aeb55398c9 100644 --- a/packages/payload/src/utilities/configToJSONSchema.ts +++ b/packages/payload/src/utilities/configToJSONSchema.ts @@ -496,6 +496,14 @@ export function fieldsToJSONSchema( enum: buildOptionEnums(field.options), } + if (field.interfaceName) { + interfaceNameDefinitions.set(field.interfaceName, fieldSchema) + + fieldSchema = { + $ref: `#/definitions/${field.interfaceName}`, + } + } + break } @@ -657,6 +665,15 @@ export function fieldsToJSONSchema( fieldSchema.enum = optionEnums } } + + if (field.interfaceName) { + interfaceNameDefinitions.set(field.interfaceName, fieldSchema) + + fieldSchema = { + $ref: `#/definitions/${field.interfaceName}`, + } + } + break } break diff --git a/test/types/config.ts b/test/types/config.ts index 648d986b9f..a929272ff1 100644 --- a/test/types/config.ts +++ b/test/types/config.ts @@ -22,6 +22,38 @@ export default buildConfigWithDefaults({ type: 'text', name: 'title', }, + { + name: 'selectField', + type: 'select', + required: true, + interfaceName: 'MySelectOptions', + options: [ + { + label: 'Option 1', + value: 'option-1', + }, + { + label: 'Option 2', + value: 'option-2', + }, + ], + }, + { + name: 'radioField', + type: 'radio', + required: true, + interfaceName: 'MyRadioOptions', + options: [ + { + label: 'Option 1', + value: 'option-1', + }, + { + label: 'Option 2', + value: 'option-2', + }, + ], + }, ], }, { diff --git a/test/types/payload-types.ts b/test/types/payload-types.ts index 1073cb4aca..98ea20265b 100644 --- a/test/types/payload-types.ts +++ b/test/types/payload-types.ts @@ -6,6 +6,16 @@ * and re-run `payload generate:types` to regenerate this file. */ +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "MySelectOptions". + */ +export type MySelectOptions = 'option-1' | 'option-2'; +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "MyRadioOptions". + */ +export type MyRadioOptions = 'option-1' | 'option-2'; /** * Supported timezones in IANA format. * @@ -132,6 +142,8 @@ export interface Post { id: string; text?: string | null; title?: string | null; + selectField: MySelectOptions; + radioField: MyRadioOptions; updatedAt: string; createdAt: string; } @@ -249,6 +261,8 @@ export interface PayloadMigration { export interface PostsSelect { text?: T; title?: T; + selectField?: T; + radioField?: T; updatedAt?: T; createdAt?: T; } diff --git a/test/types/types.spec.ts b/test/types/types.spec.ts index 3ae30c2c7e..fb315ec113 100644 --- a/test/types/types.spec.ts +++ b/test/types/types.spec.ts @@ -10,7 +10,14 @@ import type { import payload from 'payload' import { describe, expect, test } from 'tstyche' -import type { Menu, Post, User } from './payload-types.js' +import type { + Menu, + MyRadioOptions, + MySelectOptions, + Post, + SupportedTimezones, + User, +} from './payload-types.js' const asType = () => { return '' as T @@ -124,4 +131,18 @@ describe('Types testing', () => { }>() }) }) + + describe('generated types', () => { + test('has SupportedTimezones', () => { + expect().type.toBeAssignableTo() + }) + + test('has global generated options interface based on select field', () => { + expect(asType()).type.toBe() + }) + + test('has global generated options interface based on radio field', () => { + expect(asType()).type.toBe() + }) + }) })