diff --git a/packages/payload/src/collections/config/client.ts b/packages/payload/src/collections/config/client.ts index 6b79cb5ef..5ca0f2646 100644 --- a/packages/payload/src/collections/config/client.ts +++ b/packages/payload/src/collections/config/client.ts @@ -2,7 +2,7 @@ import type { LivePreviewConfig, ServerOnlyLivePreviewProperties } from '../../c export type ServerOnlyCollectionProperties = keyof Pick< SanitizedCollectionConfig, - 'access' | 'endpoints' | 'hooks' + 'access' | 'custom' | 'endpoints' | 'hooks' > export type ServerOnlyCollectionAdminProperties = keyof Pick< @@ -44,6 +44,7 @@ export const createClientCollectionConfig = ({ 'hooks', 'access', 'endpoints', + 'custom', // `upload` // `admin` // are all handled separately @@ -77,12 +78,6 @@ export const createClientCollectionConfig = ({ }) } - if ('custom' in sanitized && sanitized.custom) { - if ('server' in sanitized.custom && sanitized.custom.server) { - delete sanitized.custom.server - } - } - if ('admin' in sanitized) { sanitized.admin = { ...sanitized.admin } diff --git a/packages/payload/src/collections/config/defaults.ts b/packages/payload/src/collections/config/defaults.ts index 696e14476..7ade12204 100644 --- a/packages/payload/src/collections/config/defaults.ts +++ b/packages/payload/src/collections/config/defaults.ts @@ -10,6 +10,7 @@ export const defaults = { }, admin: { components: {}, + custom: {}, enableRichTextLink: true, enableRichTextRelationship: true, pagination: { diff --git a/packages/payload/src/collections/config/schema.ts b/packages/payload/src/collections/config/schema.ts index 8c66d78e4..c0bc467d0 100644 --- a/packages/payload/src/collections/config/schema.ts +++ b/packages/payload/src/collections/config/schema.ts @@ -57,6 +57,7 @@ const collectionSchema = joi.object().keys({ ), }), }), + custom: joi.object().pattern(joi.string(), joi.any()), defaultColumns: joi.array().items(joi.string()), description: joi.alternatives().try(joi.string(), componentSchema), enableRichTextLink: joi.boolean(), @@ -107,10 +108,7 @@ const collectionSchema = joi.object().keys({ }), joi.boolean(), ), - custom: joi.object().keys({ - client: joi.object().pattern(joi.string(), joi.any()), - server: joi.object().pattern(joi.string(), joi.any()), - }), + custom: joi.object().pattern(joi.string(), joi.any()), dbName: joi.alternatives().try(joi.string(), joi.func()), defaultSort: joi.string(), disableDuplicate: joi.bool(), diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index e8229c2e0..9c782bddf 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -246,6 +246,8 @@ export type CollectionAdminOptions = { | React.ComponentType } } + /** Extension point to add your custom data. Available in server and client. */ + custom?: Record /** * Default columns to show in list view */ @@ -314,17 +316,8 @@ export type CollectionConfig = { * Use `true` to enable with default options */ auth?: IncomingAuthType | boolean - /** Extension point to add your custom data. */ - custom?: { - /** - * Available in client bundle. - */ - client?: Record - /** - * Server only. - */ - server?: Record - } + /** Extension point to add your custom data. Server only. */ + custom?: Record /** * Default field to sort by in collection list view */ diff --git a/packages/payload/src/config/client.ts b/packages/payload/src/config/client.ts index d85901d2c..9768a9a3d 100644 --- a/packages/payload/src/config/client.ts +++ b/packages/payload/src/config/client.ts @@ -18,6 +18,7 @@ export type ServerOnlyRootProperties = keyof Pick< | 'bin' | 'cors' | 'csrf' + | 'custom' | 'db' | 'editor' | 'email' @@ -40,6 +41,7 @@ export type ClientConfig = Omit< livePreview?: Omit } collections: ClientCollectionConfig[] + custom?: Record globals: ClientGlobalConfig[] } @@ -67,6 +69,7 @@ export const createClientConfig = async ({ 'cors', 'csrf', 'email', + 'custom', // `admin`, `onInit`, `localization`, `collections`, and `globals` are all handled separately ] @@ -84,12 +87,6 @@ export const createClientConfig = async ({ }) } - if ('custom' in clientConfig && clientConfig.custom) { - if ('server' in clientConfig.custom && clientConfig.custom.server) { - delete clientConfig.custom.server - } - } - if ('admin' in clientConfig) { clientConfig.admin = { ...clientConfig.admin } diff --git a/packages/payload/src/config/defaults.ts b/packages/payload/src/config/defaults.ts index c75400475..fb522c5ac 100644 --- a/packages/payload/src/config/defaults.ts +++ b/packages/payload/src/config/defaults.ts @@ -4,6 +4,7 @@ export const defaults: Omit = { admin: { avatar: 'default', components: {}, + custom: {}, dateFormat: 'MMMM do yyyy, h:mm a', disable: false, inactivityRoute: '/logout-inactivity', diff --git a/packages/payload/src/config/schema.ts b/packages/payload/src/config/schema.ts index 85bfdb5f2..570599fba 100644 --- a/packages/payload/src/config/schema.ts +++ b/packages/payload/src/config/schema.ts @@ -57,6 +57,7 @@ export default joi.object({ joi.object().pattern(joi.string(), component), ), }), + custom: joi.object().pattern(joi.string(), joi.any()), dateFormat: joi.string(), disable: joi.bool(), inactivityRoute: joi.string(), @@ -83,10 +84,7 @@ export default joi.object({ cookiePrefix: joi.string(), cors: [joi.string().valid('*'), joi.array().items(joi.string())], csrf: joi.array().items(joi.string().allow('')).sparse(), - custom: joi.object().keys({ - client: joi.object().pattern(joi.string(), joi.any()), - server: joi.object().pattern(joi.string(), joi.any()), - }), + custom: joi.object().pattern(joi.string(), joi.any()), db: joi.any(), debug: joi.boolean(), defaultDepth: joi.number().min(0).max(30), diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts index 4a4ca355e..9f991a8b9 100644 --- a/packages/payload/src/config/types.ts +++ b/packages/payload/src/config/types.ts @@ -417,9 +417,9 @@ export type Config = { prefillOnly?: boolean } | false - /** Set account profile picture. Options: gravatar, default or a custom React component. */ avatar?: 'default' | 'gravatar' | React.ComponentType + /** * Add extra and/or replace built-in components with custom components * @@ -489,6 +489,8 @@ export type Config = { Dashboard?: AdminView } } + /** Extension point to add your custom data. Available in server and client. */ + custom?: Record /** Global date format that will be used for all dates in the Admin panel. Any valid date-fns format pattern can be used. */ dateFormat?: string /** If set to true, the entire Admin panel will be disabled. */ @@ -547,17 +549,8 @@ export type Config = { /** A whitelist array of URLs to allow Payload cookies to be accepted from as a form of CSRF protection. */ csrf?: string[] - /** Extension point to add your custom data. */ - custom?: { - /** - * Available in client bundle. - */ - client?: Record - /** - * Server only. - */ - server?: Record - } + /** Extension point to add your custom data. Server only. */ + custom?: Record /** Pass in a database adapter for use on this project. */ db: DatabaseAdapterResult diff --git a/packages/payload/src/fields/config/client.ts b/packages/payload/src/fields/config/client.ts index 066b4cd70..201e817b9 100644 --- a/packages/payload/src/fields/config/client.ts +++ b/packages/payload/src/fields/config/client.ts @@ -8,7 +8,7 @@ export type ServerOnlyFieldProperties = | 'editor' // This is a `richText` only property | 'filterOptions' // This is a `relationship` and `upload` only property | 'label' - | keyof Pick + | keyof Pick export type ServerOnlyFieldAdminProperties = keyof Pick< FieldBase['admin'], @@ -32,6 +32,7 @@ export const createClientFieldConfig = ({ 'label', 'filterOptions', // This is a `relationship` and `upload` only property 'editor', // This is a `richText` only property + 'custom', // `fields` // `blocks` // `tabs` @@ -45,12 +46,6 @@ export const createClientFieldConfig = ({ } }) - if ('custom' in field && field.custom) { - if ('server' in field.custom && field.custom.server) { - delete field.custom.server - } - } - if ('options' in field && Array.isArray(field.options)) { field.options = field.options.map((option) => { if (typeof option === 'object' && typeof option.label === 'function') { diff --git a/packages/payload/src/fields/config/schema.ts b/packages/payload/src/fields/config/schema.ts index 07c0e6bf6..ac720fea2 100644 --- a/packages/payload/src/fields/config/schema.ts +++ b/packages/payload/src/fields/config/schema.ts @@ -15,6 +15,7 @@ export const baseAdminFields = joi.object().keys({ className: joi.string(), components: baseAdminComponentFields, condition: joi.func(), + custom: joi.object().pattern(joi.string(), joi.any()), description: joi .alternatives() .try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), componentSchema), @@ -37,10 +38,7 @@ export const baseField = joi update: joi.func(), }), admin: baseAdminFields.default(), - custom: joi.object().keys({ - client: joi.object().pattern(joi.string(), joi.any()), - server: joi.object().pattern(joi.string(), joi.any()), - }), + custom: joi.object().pattern(joi.string(), joi.any()), hidden: joi.boolean().default(false), hooks: joi .object() @@ -422,10 +420,10 @@ export const blocks = baseField.keys({ .items( joi.object({ slug: joi.string().required(), - custom: joi.object().keys({ - client: joi.object().pattern(joi.string(), joi.any()), - server: joi.object().pattern(joi.string(), joi.any()), + admin: joi.object().keys({ + custom: joi.object().pattern(joi.string(), joi.any()), }), + custom: joi.object().pattern(joi.string(), joi.any()), dbName: joi.alternatives().try(joi.string(), joi.func()), fields: joi.array().items(joi.link('#field')), graphQL: joi.object().keys({ @@ -522,14 +520,12 @@ export const ui = joi.object().keys({ }) .default({}), condition: joi.func(), + custom: joi.object().pattern(joi.string(), joi.any()), position: joi.string().valid('sidebar'), width: joi.string(), }) .default(), - custom: joi.object().keys({ - client: joi.object().pattern(joi.string(), joi.any()), - server: joi.object().pattern(joi.string(), joi.any()), - }), + custom: joi.object().pattern(joi.string(), joi.any()), label: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])), }) diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index 8af130f20..9303a4b55 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -124,6 +124,8 @@ type Admin = { * This is also run on the server, to determine if the field should be validated. */ condition?: Condition + /** Extension point to add your custom data. Available in server and client. */ + custom?: Record description?: Description disableBulkEdit?: boolean disabled?: boolean @@ -179,17 +181,8 @@ export interface FieldBase { update?: FieldAccess } admin?: Admin - /** Extension point to add your custom data. */ - custom?: { - /** - * Available in client bundle. - */ - client?: Record - /** - * Server only. - */ - server?: Record - } + /** Extension point to add your custom data. Server only. */ + custom?: Record defaultValue?: any hidden?: boolean hooks?: { @@ -430,21 +423,14 @@ export type UIField = { Filter?: React.ComponentType } condition?: Condition + /** Extension point to add your custom data. Available in server and client. */ + custom?: Record disableBulkEdit?: boolean position?: string width?: string } - /** Extension point to add your custom data. */ - custom?: { - /** - * Available in client bundle. - */ - client?: Record - /** - * Server only. - */ - server?: Record - } + /** Extension point to add your custom data. Server only. */ + custom?: Record label?: Record | string name: string type: 'ui' @@ -655,17 +641,12 @@ export type RadioField = FieldBase & { } export type Block = { - /** Extension point to add your custom data. */ - custom?: { - /** - * Available in client bundle. - */ - client?: Record - /** - * Server only. - */ - server?: Record + admin?: { + /** Extension point to add your custom data. Available in server and client. */ + custom?: Record } + /** Extension point to add your custom data. Server only. */ + custom?: Record /** * Customize the SQL table name */ diff --git a/packages/payload/src/globals/config/client.ts b/packages/payload/src/globals/config/client.ts index 0a1a7aecd..dc3b12c69 100644 --- a/packages/payload/src/globals/config/client.ts +++ b/packages/payload/src/globals/config/client.ts @@ -12,7 +12,7 @@ import { createClientFieldConfigs } from '../../fields/config/client.js' export type ServerOnlyGlobalProperties = keyof Pick< SanitizedGlobalConfig, - 'access' | 'admin' | 'endpoints' | 'fields' | 'hooks' + 'access' | 'admin' | 'custom' | 'endpoints' | 'fields' | 'hooks' > export type ServerOnlyGlobalAdminProperties = keyof Pick< SanitizedGlobalConfig['admin'], @@ -46,6 +46,7 @@ export const createClientGlobalConfig = ({ 'hooks', 'access', 'endpoints', + 'custom', // `admin` is handled separately ] @@ -55,12 +56,6 @@ export const createClientGlobalConfig = ({ } }) - if ('custom' in sanitized && sanitized.custom) { - if ('server' in sanitized.custom && sanitized.custom.server) { - delete sanitized.custom.server - } - } - if ('admin' in sanitized) { sanitized.admin = { ...sanitized.admin } diff --git a/packages/payload/src/globals/config/schema.ts b/packages/payload/src/globals/config/schema.ts index 95ab99451..7a40ebca1 100644 --- a/packages/payload/src/globals/config/schema.ts +++ b/packages/payload/src/globals/config/schema.ts @@ -39,6 +39,7 @@ const globalSchema = joi ), }), }), + custom: joi.object().pattern(joi.string(), joi.any()), description: joi.alternatives().try(joi.string(), componentSchema), group: joi .alternatives() @@ -48,10 +49,7 @@ const globalSchema = joi livePreview: joi.object(livePreviewSchema), preview: joi.func(), }), - custom: joi.object().keys({ - client: joi.object().pattern(joi.string(), joi.any()), - server: joi.object().pattern(joi.string(), joi.any()), - }), + custom: joi.object().pattern(joi.string(), joi.any()), dbName: joi.alternatives().try(joi.string(), joi.func()), endpoints: endpointsSchema, fields: joi.array(), diff --git a/packages/payload/src/globals/config/types.ts b/packages/payload/src/globals/config/types.ts index a74a25d53..78fc9ce55 100644 --- a/packages/payload/src/globals/config/types.ts +++ b/packages/payload/src/globals/config/types.ts @@ -106,6 +106,8 @@ export type GlobalAdminOptions = { Edit?: EditConfig } } + /** Extension point to add your custom data. Available in server and client. */ + custom?: Record /** * Custom description for collection */ @@ -140,17 +142,8 @@ export type GlobalConfig = { update?: Access } admin?: GlobalAdminOptions - /** Extension point to add your custom data. */ - custom?: { - /** - * Available in client bundle. - */ - client?: Record - /** - * Server only. - */ - server?: Record - } + /** Extension point to add your custom data. Server only. */ + custom?: Record /** * Customize the SQL table name */ diff --git a/packages/ui/src/fields/shared/index.tsx b/packages/ui/src/fields/shared/index.tsx index a0d06e783..6df1efb22 100644 --- a/packages/ui/src/fields/shared/index.tsx +++ b/packages/ui/src/fields/shared/index.tsx @@ -17,9 +17,7 @@ export type FormFieldBase = { CustomError?: React.ReactNode CustomLabel?: React.ReactNode className?: string - custom?: { - client?: Record - } + custom?: Record descriptionProps?: FieldDescriptionProps disabled?: boolean docPreferences?: DocumentPreferences diff --git a/packages/ui/src/forms/FieldPropsProvider/index.tsx b/packages/ui/src/forms/FieldPropsProvider/index.tsx index 0d08d1833..2619ed5f4 100644 --- a/packages/ui/src/forms/FieldPropsProvider/index.tsx +++ b/packages/ui/src/forms/FieldPropsProvider/index.tsx @@ -5,9 +5,7 @@ import type { FieldPermissions } from 'payload/types' import React from 'react' export type FieldPropsContextType = { - custom?: { - client?: Record - } + custom?: Record indexPath?: string path: string permissions?: FieldPermissions @@ -21,6 +19,7 @@ export type FieldPropsContextType = { const FieldPropsContext = React.createContext({ type: '' as keyof FieldTypes, + custom: {}, indexPath: '', path: '', permissions: {} as FieldPermissions, @@ -31,9 +30,7 @@ const FieldPropsContext = React.createContext({ export type Props = { children: React.ReactNode - custom?: { - client?: Record - } + custom?: Record indexPath?: string path: string permissions?: FieldPermissions diff --git a/packages/ui/src/forms/RenderFields/RenderField.tsx b/packages/ui/src/forms/RenderFields/RenderField.tsx index 2084a5078..8ea1a99df 100644 --- a/packages/ui/src/forms/RenderFields/RenderField.tsx +++ b/packages/ui/src/forms/RenderFields/RenderField.tsx @@ -17,9 +17,7 @@ import { FieldPropsProvider, useFieldProps } from '../FieldPropsProvider/index.j type Props = { CustomField: MappedField['CustomField'] - custom?: { - client?: Record - } + custom?: Record disabled: boolean fieldComponentProps?: FieldComponentProps indexPath?: string diff --git a/packages/ui/src/forms/RenderFields/index.tsx b/packages/ui/src/forms/RenderFields/index.tsx index 49fcd83ba..6a9cf9360 100644 --- a/packages/ui/src/forms/RenderFields/index.tsx +++ b/packages/ui/src/forms/RenderFields/index.tsx @@ -60,6 +60,7 @@ export const RenderFields: React.FC = (props) => { const { type, CustomField, + custom, disabled, fieldComponentProps, fieldComponentProps: { readOnly }, @@ -71,7 +72,7 @@ export const RenderFields: React.FC = (props) => { return ( ) : undefined, cellComponentProps, - custom: field.custom, + custom: field?.admin?.custom, disableBulkEdit: 'admin' in field && 'disableBulkEdit' in field.admin && field.admin.disableBulkEdit, fieldComponentProps, diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/types.ts b/packages/ui/src/providers/ComponentMap/buildComponentMap/types.ts index e857309e5..69c884141 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/types.ts +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/types.ts @@ -35,6 +35,7 @@ export type MappedTab = { } export type ReducedBlock = { + custom?: Record fieldMap: FieldMap imageAltText?: string imageURL?: string @@ -68,9 +69,7 @@ export type MappedField = { CustomCell?: React.ReactNode CustomField?: React.ReactNode cellComponentProps: CellComponentProps - custom?: { - client?: Record - } + custom?: Record disableBulkEdit?: boolean disabled?: boolean fieldComponentProps: FieldComponentProps diff --git a/test/config/config.ts b/test/config/config.ts index c159990b0..4fca7bead 100644 --- a/test/config/config.ts +++ b/test/config/config.ts @@ -26,8 +26,7 @@ export default buildConfigWithDefaults({ name: 'title', type: 'text', custom: { - client: { description: 'The title of this page' }, - server: { description: 'The title of this page' }, + description: 'The title of this page', }, }, { @@ -47,20 +46,17 @@ export default buildConfigWithDefaults({ }, ], custom: { - client: { description: 'The blockOne of this page' }, - server: { description: 'The blockOne of this page' }, + description: 'The blockOne of this page', }, }, ], custom: { - client: { description: 'The blocks of this page' }, - server: { description: 'The blocks of this page' }, + description: 'The blocks of this page', }, }, ], custom: { - client: { externalLink: 'https://foo.bar' }, - server: { externalLink: 'https://foo.bar' }, + externalLink: 'https://foo.bar', }, }, ], @@ -83,12 +79,11 @@ export default buildConfigWithDefaults({ name: 'title', type: 'text', custom: { - client: { description: 'The title of my global' }, - server: { description: 'The title of my global' }, + description: 'The title of my global', }, }, ], - custom: { client: { foo: 'bar' }, server: { foo: 'bar' } }, + custom: { foo: 'bar' }, }, ], endpoints: [ @@ -101,7 +96,7 @@ export default buildConfigWithDefaults({ custom: { description: 'Get the sanitized payload config' }, }, ], - custom: { client: { name: 'Customer portal' }, server: { name: 'Customer portal' } }, + custom: { name: 'Customer portal' }, onInit: async (payload) => { await payload.create({ collection: 'users', diff --git a/test/config/int.spec.ts b/test/config/int.spec.ts index 8052922ac..b02acbd9a 100644 --- a/test/config/int.spec.ts +++ b/test/config/int.spec.ts @@ -21,8 +21,7 @@ describe('Config', () => { it('allows a custom field at the config root', () => { const { config } = payload expect(config.custom).toEqual({ - client: { name: 'Customer portal' }, - server: { name: 'Customer portal' }, + name: 'Customer portal', }) }) @@ -39,8 +38,7 @@ describe('Config', () => { it('allows a custom field in collections', () => { const [collection] = payload.config.collections expect(collection.custom).toEqual({ - client: { externalLink: 'https://foo.bar' }, - server: { externalLink: 'https://foo.bar' }, + externalLink: 'https://foo.bar', }) }) @@ -57,11 +55,8 @@ describe('Config', () => { const [collection] = payload.config.collections const [field] = collection.fields - console.log({ custom: field.custom }) - expect(field.custom).toEqual({ - client: { description: 'The title of this page' }, - server: { description: 'The title of this page' }, + description: 'The title of this page', }) }) @@ -70,8 +65,7 @@ describe('Config', () => { const [, blocksField] = collection.fields expect((blocksField as BlockField).blocks[0].custom).toEqual({ - server: { description: 'The blockOne of this page' }, - client: { description: 'The blockOne of this page' }, + description: 'The blockOne of this page', }) }) }) @@ -79,7 +73,7 @@ describe('Config', () => { describe('global config', () => { it('allows a custom field in globals', () => { const [global] = payload.config.globals - expect(global.custom).toEqual({ client: { foo: 'bar' }, server: { foo: 'bar' } }) + expect(global.custom).toEqual({ foo: 'bar' }) }) it('allows a custom field in global endpoints', () => { @@ -94,8 +88,7 @@ describe('Config', () => { const [field] = global.fields expect(field.custom).toEqual({ - client: { description: 'The title of my global' }, - server: { description: 'The title of my global' }, + description: 'The title of my global', }) }) }) diff --git a/test/fields/collections/UI/UICustomClient.tsx b/test/fields/collections/UI/UICustomClient.tsx index 1e2757050..08a355d72 100644 --- a/test/fields/collections/UI/UICustomClient.tsx +++ b/test/fields/collections/UI/UICustomClient.tsx @@ -4,7 +4,6 @@ import React from 'react' export const UICustomClient: React.FC = () => { const { custom, path } = useFieldProps() - const client = custom?.client - return
{client?.customValue}
+ return
{custom?.customValue}
} diff --git a/test/fields/collections/UI/index.ts b/test/fields/collections/UI/index.ts index 34d58a27e..95bd6dfc4 100644 --- a/test/fields/collections/UI/index.ts +++ b/test/fields/collections/UI/index.ts @@ -7,6 +7,12 @@ const UIFields: CollectionConfig = { slug: uiFieldsSlug, admin: { useAsTitle: 'text', + custom: { + 'new-value': 'client available', + }, + }, + custom: { + 'new-server-value': 'only available on server', }, defaultSort: 'id', fields: [ @@ -22,13 +28,13 @@ const UIFields: CollectionConfig = { components: { Field: UICustomClient, }, - }, - custom: { - client: { + custom: { customValue: `client-side-configuration`, }, + }, + custom: { server: { - 'new-server-value': 'only available on server', + serverOnly: 'string', }, }, }, diff --git a/test/fields/config.ts b/test/fields/config.ts index 2316a83bc..1f459db68 100644 --- a/test/fields/config.ts +++ b/test/fields/config.ts @@ -82,6 +82,13 @@ export default buildConfigWithDefaults({ 'new-server-value': 'only available on server', }, }, + admin: { + custom: { + client: { + 'new-value': 'client available', + }, + }, + }, localization: { defaultLocale: 'en', fallback: true,