diff --git a/docs/fields/relationship.mdx b/docs/fields/relationship.mdx index 3d567bc790..a84370cdf8 100644 --- a/docs/fields/relationship.mdx +++ b/docs/fields/relationship.mdx @@ -26,28 +26,28 @@ keywords: relationship, fields, config, configuration, documentation, Content Ma ### Config -| Option | Description | -| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | -| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. | -| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). | -| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. | -| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. | -| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. | -| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](/docs/getting-started/concepts#depth) | -| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | -| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | -| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | -| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | -| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | -| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | -| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | -| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | -| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | -| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | +| Option | Description | +|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) | +| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. | +| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). | +| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. | +| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. | +| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. | +| **`maxDepth`** | Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/getting-started/concepts#field-level-max-depth) | +| **`label`** | Text used as a field label in the Admin panel or an object with keys for each language. | +| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. | +| **`validate`** | Provide a custom validation function that will be executed on both the Admin panel and the backend. [More](/docs/fields/overview#validation) | +| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. | +| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/config), include its data in the user JWT. | +| **`hooks`** | Provide field-based hooks to control logic for this field. [More](/docs/fields/overview#field-level-hooks) | +| **`access`** | Provide field-based access control to denote what users can see and do with this field's data. [More](/docs/fields/overview#field-level-access-control) | +| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin panel. | +| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) | +| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | +| **`required`** | Require this field to have a value. | +| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-config) for more details. | +| **`custom`** | Extension point for adding custom data (e.g. for plugins) | _\* An asterisk denotes that a property is required._ diff --git a/docs/getting-started/concepts.mdx b/docs/getting-started/concepts.mdx index b87bee24ee..582e862aab 100644 --- a/docs/getting-started/concepts.mdx +++ b/docs/getting-started/concepts.mdx @@ -156,6 +156,28 @@ To populate `user.author.department` in it's entirety you could specify `?depth= } ``` +#### Field-level max depth + +Fields like relationships or uploads can have a `maxDepth` property that limits the depth of the population for that field. Here are some examples: + +Depth: 10 +Current depth when field is accessed: 1 +`maxDepth`: undefined + +In this case, the field would be populated to 9 levels of population. + +Depth: 10 +Current depth when field is accessed: 0 +`maxDepth`: 2 + +In this case, the field would be populated to 2 levels of population, despite there being a remaining depth of 8. + +Depth: 10 +Current depth when field is accessed: 2 +`maxDepth`: 1 + +In this case, the field would not be populated, as the current depth (2) has exceeded the `maxDepth` for this field (1). + Note:
diff --git a/packages/graphql/src/schema/buildObjectType.ts b/packages/graphql/src/schema/buildObjectType.ts index dc57e686a1..68441c775d 100644 --- a/packages/graphql/src/schema/buildObjectType.ts +++ b/packages/graphql/src/schema/buildObjectType.ts @@ -490,9 +490,12 @@ function buildObjectType({ if (editor?.populationPromises) { const fieldPromises = [] const populationPromises = [] + const populateDepth = + field?.maxDepth !== undefined && field?.maxDepth < depth ? field?.maxDepth : depth + editor?.populationPromises({ context, - depth, + depth: populateDepth, draft: args.draft, field, fieldPromises, diff --git a/packages/payload/src/fields/config/schema.ts b/packages/payload/src/fields/config/schema.ts index ac720fea26..ba42beb64c 100644 --- a/packages/payload/src/fields/config/schema.ts +++ b/packages/payload/src/fields/config/schema.ts @@ -477,6 +477,7 @@ export const richText = baseField.keys({ validate: joi.func().required(), }) .unknown(), + maxDepth: joi.number(), }) export const date = baseField.keys({ diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index 46570cab95..0204a19d38 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -446,6 +446,11 @@ export type UploadField = FieldBase & { } } filterOptions?: FilterOptions + /** + * Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. + * + * {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth} + */ maxDepth?: number relationTo: string type: 'upload' @@ -506,6 +511,11 @@ export type SelectField = FieldBase & { type SharedRelationshipProperties = FieldBase & { filterOptions?: FilterOptions hasMany?: boolean + /** + * Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. + * + * {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth} + */ maxDepth?: number type: 'relationship' } & ( @@ -588,6 +598,12 @@ export type RichTextField< editor?: | RichTextAdapter | RichTextAdapterProvider + /** + * Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. + * + * {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth} + */ + maxDepth?: number type: 'richText' } & ExtraProperties diff --git a/packages/payload/src/fields/hooks/afterRead/promise.ts b/packages/payload/src/fields/hooks/afterRead/promise.ts index 2fec04ae9c..4d7af2bf18 100644 --- a/packages/payload/src/fields/hooks/afterRead/promise.ts +++ b/packages/payload/src/fields/hooks/afterRead/promise.ts @@ -150,10 +150,13 @@ export const promise = async ({ const editor: RichTextAdapter = field?.editor // This is run here AND in the GraphQL Resolver if (editor?.populationPromises) { + const populateDepth = + field?.maxDepth !== undefined && field?.maxDepth < depth ? field?.maxDepth : depth + editor.populationPromises({ context, currentDepth, - depth, + depth: populateDepth, draft, field, fieldPromises, diff --git a/packages/richtext-lexical/src/field/features/link/drawer/baseFields.ts b/packages/richtext-lexical/src/field/features/link/drawer/baseFields.ts index a7a34df2fb..5438f17f41 100644 --- a/packages/richtext-lexical/src/field/features/link/drawer/baseFields.ts +++ b/packages/richtext-lexical/src/field/features/link/drawer/baseFields.ts @@ -8,6 +8,7 @@ export const getBaseFields = ( config: SanitizedConfig, enabledCollections: false | string[], disabledCollections: false | string[], + maxDepth?: number, ): FieldWithRichTextRequiredEditor[] => { let enabledRelations: string[] @@ -97,6 +98,7 @@ export const getBaseFields = ( } : null, label: ({ t }) => t('fields:chooseDocumentToLink'), + maxDepth, relationTo: enabledRelations, required: true, }) diff --git a/packages/richtext-lexical/src/field/features/link/feature.server.ts b/packages/richtext-lexical/src/field/features/link/feature.server.ts index 889d8ab740..d8f6dd199d 100644 --- a/packages/richtext-lexical/src/field/features/link/feature.server.ts +++ b/packages/richtext-lexical/src/field/features/link/feature.server.ts @@ -50,6 +50,13 @@ export type LinkFeatureServerProps = ExclusiveLinkCollectionsProps & { defaultFields: FieldWithRichTextRequiredEditor[] }) => FieldWithRichTextRequiredEditor[]) | FieldWithRichTextRequiredEditor[] + /** + * Sets a maximum population depth for the internal doc default field of link, regardless of the remaining depth when the field is reached. + * This behaves exactly like the maxDepth properties of relationship and upload fields. + * + * {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth} + */ + maxDepth?: number } export const LinkFeature: FeatureProviderProviderServer = ( @@ -67,6 +74,7 @@ export const LinkFeature: FeatureProviderProviderServer = ({ - currentDepth, - depth, - draft, - field, - node, - overrideAccess, - populationPromises, - req, - showHiddenFields, -}) => { - if (node?.value) { - // @ts-expect-error - const id = node?.value?.id || node?.value // for backwards-compatibility +export const relationshipPopulationPromiseHOC = ( + props: RelationshipFeatureProps, +): PopulationPromise => { + const relationshipPopulationPromise: PopulationPromise = ({ + currentDepth, + depth, + draft, + field, + node, + overrideAccess, + populationPromises, + req, + showHiddenFields, + }) => { + if (node?.value) { + // @ts-expect-error + const id = node?.value?.id || node?.value // for backwards-compatibility - const collection = req.payload.collections[node?.relationTo] + const collection = req.payload.collections[node?.relationTo] - if (collection) { - populationPromises.push( - populate({ - id, - collection, - currentDepth, - data: node, - depth, - draft, - field, - key: 'value', - overrideAccess, - req, - showHiddenFields, - }), - ) + if (collection) { + const populateDepth = + props?.maxDepth !== undefined && props?.maxDepth < depth ? props?.maxDepth : depth + + populationPromises.push( + populate({ + id, + collection, + currentDepth, + data: node, + depth: populateDepth, + draft, + field, + key: 'value', + overrideAccess, + req, + showHiddenFields, + }), + ) + } } } + + return relationshipPopulationPromise } diff --git a/packages/richtext-lexical/src/field/features/upload/feature.server.ts b/packages/richtext-lexical/src/field/features/upload/feature.server.ts index ff80f5ba1b..33681c15ae 100644 --- a/packages/richtext-lexical/src/field/features/upload/feature.server.ts +++ b/packages/richtext-lexical/src/field/features/upload/feature.server.ts @@ -26,6 +26,13 @@ export type UploadFeatureProps = { fields: FieldWithRichTextRequiredEditor[] } } + /** + * Sets a maximum population depth for this upload (not the fields for this upload), regardless of the remaining depth when the respective field is reached. + * This behaves exactly like the maxDepth properties of relationship and upload fields. + * + * {@link https://payloadcms.com/docs/getting-started/concepts#field-level-max-depth} + */ + maxDepth?: number } /** diff --git a/packages/richtext-lexical/src/field/features/upload/populationPromise.ts b/packages/richtext-lexical/src/field/features/upload/populationPromise.ts index f0577ae0d1..f72cccfdc0 100644 --- a/packages/richtext-lexical/src/field/features/upload/populationPromise.ts +++ b/packages/richtext-lexical/src/field/features/upload/populationPromise.ts @@ -31,13 +31,16 @@ export const uploadPopulationPromiseHOC = ( // @ts-expect-error const id = node?.value?.id || node?.value // for backwards-compatibility + const populateDepth = + props?.maxDepth !== undefined && props?.maxDepth < depth ? props?.maxDepth : depth + populationPromises.push( populate({ id, collection, currentDepth, data: node, - depth, + depth: populateDepth, draft, field, key: 'value',