diff --git a/docs/fields/overview.mdx b/docs/fields/overview.mdx index fcd346505..eed310084 100644 --- a/docs/fields/overview.mdx +++ b/docs/fields/overview.mdx @@ -356,7 +356,7 @@ For full details on Admin Options, see the [Field Admin Options](../admin/fields ## Custom ID Fields -All [Collections](../configuration/collections) automatically generate their own ID field. If needed, you can override this behavior by providing an explicit ID field to your config. This will force users to provide a their own ID value when creating a record. +All [Collections](../configuration/collections) automatically generate their own ID field. If needed, you can override this behavior by providing an explicit ID field to your config. This field should either be required or have a hook to generate the ID dynamically. To define a custom ID field, add a new field with the `name` property set to `id`: @@ -368,6 +368,7 @@ export const MyCollection: CollectionConfig = { fields: [ { name: 'id', // highlight-line + required: true, type: 'number', }, ], diff --git a/packages/db-mongodb/src/create.ts b/packages/db-mongodb/src/create.ts index d1ac9a682..504cffd85 100644 --- a/packages/db-mongodb/src/create.ts +++ b/packages/db-mongodb/src/create.ts @@ -20,6 +20,10 @@ export const create: Create = async function create( fields: this.payload.collections[collection].config.fields, }) + if (this.payload.collections[collection].customIDType) { + sanitizedData._id = sanitizedData.id + } + try { ;[doc] = await Model.create([sanitizedData], options) } catch (error) { diff --git a/packages/payload/src/collections/operations/create.ts b/packages/payload/src/collections/operations/create.ts index 574d3e2c6..a5580200f 100644 --- a/packages/payload/src/collections/operations/create.ts +++ b/packages/payload/src/collections/operations/create.ts @@ -123,17 +123,6 @@ export const createOperation = async < await executeAccess({ data, req }, collectionConfig.access.create) } - // ///////////////////////////////////// - // Custom id - // ///////////////////////////////////// - - if (payload.collections[collectionConfig.slug].customIDType) { - data = { - _id: data.id, - ...data, - } - } - // ///////////////////////////////////// // Generate data for all files and sizes // ///////////////////////////////////// diff --git a/test/database/config.ts b/test/database/config.ts index 2d23bd29a..641dd7b35 100644 --- a/test/database/config.ts +++ b/test/database/config.ts @@ -4,6 +4,8 @@ const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) import type { TextField } from 'payload' +import { v4 as uuid } from 'uuid' + import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' import { devUser } from '../credentials.js' @@ -356,6 +358,29 @@ export default buildConfigWithDefaults({ }, ], }, + { + slug: 'custom-ids', + fields: [ + { + name: 'id', + type: 'text', + admin: { + readOnly: true, + }, + hooks: { + beforeChange: [ + ({ value, operation }) => { + if (operation === 'create') { + return uuid() + } + return value + }, + ], + }, + }, + ], + versions: { drafts: true }, + }, ], globals: [ { diff --git a/test/database/int.spec.ts b/test/database/int.spec.ts index 46a40ae76..532eecf57 100644 --- a/test/database/int.spec.ts +++ b/test/database/int.spec.ts @@ -75,10 +75,19 @@ describe('database', () => { expect(updated.id).toStrictEqual(created.doc.id) }) + + it('should create with generated ID text from hook', async () => { + const doc = await payload.create({ + collection: 'custom-ids', + data: {}, + }) + + expect(doc.id).toBeDefined() + }) }) describe('timestamps', () => { - it('should have createdAt and updatedAt timetstamps to the millisecond', async () => { + it('should have createdAt and updatedAt timestamps to the millisecond', async () => { const result = await payload.create({ collection: 'posts', data: { diff --git a/test/database/payload-types.ts b/test/database/payload-types.ts index 354c1c835..21259d61c 100644 --- a/test/database/payload-types.ts +++ b/test/database/payload-types.ts @@ -19,21 +19,44 @@ export interface Config { 'custom-schema': CustomSchema; places: Place; 'fields-persistance': FieldsPersistance; + 'custom-ids': CustomId; users: User; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; 'payload-migrations': PayloadMigration; }; + collectionsSelect: { + posts: PostsSelect | PostsSelect; + 'default-values': DefaultValuesSelect | DefaultValuesSelect; + 'relation-a': RelationASelect | RelationASelect; + 'relation-b': RelationBSelect | RelationBSelect; + 'pg-migrations': PgMigrationsSelect | PgMigrationsSelect; + 'custom-schema': CustomSchemaSelect | CustomSchemaSelect; + places: PlacesSelect | PlacesSelect; + 'fields-persistance': FieldsPersistanceSelect | FieldsPersistanceSelect; + 'custom-ids': CustomIdsSelect | CustomIdsSelect; + users: UsersSelect | UsersSelect; + 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; + 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; + 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; + }; db: { defaultIDType: string; }; globals: { global: Global; }; + globalsSelect: { + global: GlobalSelect | GlobalSelect; + }; locale: 'en' | 'es'; user: User & { collection: 'users'; }; + jobs?: { + tasks: unknown; + workflows?: unknown; + }; } export interface UserAuthOperations { forgotPassword: { @@ -232,6 +255,15 @@ export interface FieldsPersistance { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "custom-ids". + */ +export interface CustomId { + id: string; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users". @@ -288,6 +320,10 @@ export interface PayloadLockedDocument { relationTo: 'fields-persistance'; value: string | FieldsPersistance; } | null) + | ({ + relationTo: 'custom-ids'; + value: string | CustomId; + } | null) | ({ relationTo: 'users'; value: string | User; @@ -334,6 +370,215 @@ export interface PayloadMigration { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "posts_select". + */ +export interface PostsSelect { + title?: T; + hasTransaction?: T; + throwAfterChange?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "default-values_select". + */ +export interface DefaultValuesSelect { + title?: T; + defaultValue?: T; + array?: + | T + | { + defaultValue?: T; + id?: T; + }; + group?: + | T + | { + defaultValue?: T; + }; + select?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "relation-a_select". + */ +export interface RelationASelect { + title?: T; + richText?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "relation-b_select". + */ +export interface RelationBSelect { + title?: T; + relationship?: T; + richText?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "pg-migrations_select". + */ +export interface PgMigrationsSelect { + relation1?: T; + myArray?: + | T + | { + relation2?: T; + mySubArray?: + | T + | { + relation3?: T; + id?: T; + }; + id?: T; + }; + myGroup?: + | T + | { + relation4?: T; + }; + myBlocks?: + | T + | { + myBlock?: + | T + | { + relation5?: T; + relation6?: T; + id?: T; + blockName?: T; + }; + }; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "custom-schema_select". + */ +export interface CustomSchemaSelect { + text?: T; + localizedText?: T; + relationship?: T; + select?: T; + radio?: T; + array?: + | T + | { + text?: T; + localizedText?: T; + id?: T; + }; + blocks?: + | T + | { + block?: + | T + | { + text?: T; + localizedText?: T; + id?: T; + blockName?: T; + }; + }; + updatedAt?: T; + createdAt?: T; + _status?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "places_select". + */ +export interface PlacesSelect { + country?: T; + city?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "fields-persistance_select". + */ +export interface FieldsPersistanceSelect { + text?: T; + textHooked?: T; + array?: + | T + | { + id?: T; + }; + textWithinRow?: T; + textWithinCollapsible?: T; + textWithinTabs?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "custom-ids_select". + */ +export interface CustomIdsSelect { + id?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "users_select". + */ +export interface UsersSelect { + updatedAt?: T; + createdAt?: T; + email?: T; + resetPasswordToken?: T; + resetPasswordExpiration?: T; + salt?: T; + hash?: T; + loginAttempts?: T; + lockUntil?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-locked-documents_select". + */ +export interface PayloadLockedDocumentsSelect { + document?: T; + globalSlug?: T; + user?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-preferences_select". + */ +export interface PayloadPreferencesSelect { + user?: T; + key?: T; + value?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "payload-migrations_select". + */ +export interface PayloadMigrationsSelect { + name?: T; + batch?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "global". @@ -344,6 +589,16 @@ export interface Global { updatedAt?: string | null; createdAt?: string | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "global_select". + */ +export interface GlobalSelect { + text?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "auth".