关联字段

关联字段(Relationship Field)是 Payload 中最强大的字段之一。它能够轻松地将文档相互关联起来。

展示 Payload 管理面板中的关系字段
管理面板中关系字段的截图

关联字段有多种用途,包括:

  • Product 文档添加到 Order 文档中
  • 允许 Order 通过 placedBy 关系关联到 OrganizationUser 集合
  • Post 文档分配 Category 文档

要添加关联字段,只需在字段配置中将 type 设置为 relationship

import type { Field } from 'payload'

export const MyRelationshipField: Field = {
  // ...
  // highlight-start
  type: 'relationship',
  relationTo: 'products',
  // highlight-end
}

配置选项

选项描述
name *作为属性名用于数据库存储和检索。了解更多
relationTo *提供一个或多个 collection 的 slug,用于建立关联关系。
filterOptions用于筛选 UI 中显示选项及验证的查询。了解更多
hasMany布尔值,设置为 true 时允许该字段关联多个文档而非仅一个。
minRows当存在值时,验证允许的最小项目数。与 hasMany 配合使用。
maxRows当存在值时,验证允许的最大项目数。与 hasMany 配合使用。
maxDepth设置该字段的最大填充深度,无论到达该字段时的剩余深度如何。最大深度
label在 Admin Panel 中用作字段标签的文本,或为每种语言提供键值对的对象。
unique强制 Collection 中每个条目该字段的值必须唯一。
validate提供自定义验证函数,将在 Admin Panel 和后台执行。了解更多
index为该字段构建索引以加快查询速度。如果用户会频繁查询该字段数据,请设置为 true
saveToJWT如果该字段是顶层字段且嵌套在支持身份验证的配置中,则将其数据包含在用户 JWT 中。
hooks提供字段钩子来控制该字段的逻辑。更多详情
access提供字段访问控制,指定用户可以查看和操作该字段数据的权限。更多详情
hidden完全限制该字段在所有 API 中的可见性。仍会保存到数据库,但不会出现在任何 API 或 Admin Panel 中。
defaultValue提供用于该字段默认值的数据。了解更多
localized启用该字段的本地化功能。需要在基础配置中启用本地化
required要求该字段必须有值。
admin管理员特定配置。更多详情
custom用于添加自定义数据(如插件)的扩展点
typescriptSchema通过提供 JSON schema 来覆盖字段类型生成
virtual设置为 true 可禁用数据库中的字段,或提供字符串路径以将虚拟字段与关联关系链接。参见虚拟字段
graphQL该字段的自定义 graphQL 配置。更多详情

* 星号表示该属性为必填项。

提示: 深度参数可用于自动填充 API 返回的关联文档。

管理选项

要自定义 Relationship Field 在管理面板中的外观和行为,你可以使用 admin 选项:

import type { Field } from 'payload'

export const MyRelationshipField: Field = {
  // ...
  admin: {
    // highlight-line
    // ...
  },
}

Relationship Field 继承了基础字段管理配置中的所有默认选项,并额外支持以下选项:

属性描述
isSortable设置为 true 可允许在管理界面中通过拖拽对此字段进行排序(仅在 hasMany 设为 true 时有效)。
allowCreate设置为 false 可禁止在关系字段内创建新文档。
allowEdit设置为 false 可禁止在关系字段内编辑文档。
sortOptions为关系字段下拉选项定义默认排序规则。了解更多
placeholder自定义文本或函数来替换默认占位符
appearance设置为 drawerselect 可改变字段行为。默认为 select

排序选项

你可以通过两种方式指定 sortOptions

作为字符串:

提供一个字符串来定义所有关系字段下拉框的全局默认排序字段。可以在字段名前加上减号("-")表示降序排序。

示例:

sortOptions: 'fieldName',

此配置将使所有关系字段下拉框按 "fieldName" 升序排序。

作为对象:

指定一个对象,其中键是集合 slugs,值是要排序的字段名字符串。这允许为每个集合的关系下拉框设置不同的排序字段。

示例:

sortOptions: {
  "pages": "fieldName1",
  "posts": "-fieldName2",
  "categories": "fieldName3"
}

在此配置中:

  • pages 相关的下拉框将按 "fieldName1" 升序排序。
  • posts 的下拉框将使用 "fieldName2" 降序排序("-"前缀表示)。
  • categories 关联的下拉框将基于 "fieldName3" 升序排序。

注意:如果未定义 sortOptions,将使用关系字段下拉框的默认排序行为。

关系选项过滤

可以通过提供查询约束来动态限制选项,该约束将用于验证输入和过滤UI中可用的关系。

filterOptions属性可以是一个Where查询,也可以是一个返回true表示不过滤、false表示阻止所有,或者返回Where查询的函数。当使用函数时,它将接收一个包含以下属性的参数对象:

属性描述
blockData最近父块的数据。如果字段不在块内或在列表视图中的Filter组件上调用时,将为undefined
data包含当前正在编辑的完整collection或global文档的对象。在列表视图中的Filter组件上调用时,将为空对象。
id当前正在编辑文档的id。在create操作期间或在列表视图中的Filter组件上调用时,将为undefined
relationTo要过滤的collection slug,限于此字段的relationTo属性。
reqPayload请求对象,包含对payloaduserlocale等的引用。
siblingData包含仅限此字段同一父级下字段的文档数据的对象。在列表视图中的Filter组件上调用时,将为空对象。
user包含当前认证用户的对象。

示例

import type { CollectionConfig } from 'payload'

export const ExampleCollection: CollectionConfig = {
  slug: 'example-collection',
  fields: [
    {
      name: 'purchase',
      type: 'relationship',
      relationTo: ['products', 'services'],
      filterOptions: ({ relationTo, siblingData }) => {
        // 根据关系类型动态返回 Where 查询
        if (relationTo === 'products') {
          return {
            stock: { greater_than: siblingData.quantity },
          }
        }

        if (relationTo === 'services') {
          return {
            isAvailable: { equals: true },
          }
        }
      },
    },
  ],
}

你可以在这里了解更多关于编写查询的信息查询文档

注意:

当 relationship 字段同时具有 filterOptions 和自定义的

validate 函数时,API 不会验证 filterOptions 除非你在 validate 函数中调用从

payload/shared 导入的默认 relationship 字段验证函数。

双向关系

单独的 relationship 字段用于定义包含该 relationship 字段的文档的关系,这可以被视为"单向"关系。例如,如果你有一个 Post 文档,它上面有一个 category relationship 字段,相关的 category 本身不会显示任何关于设置了该 category 的 posts 的信息。

然而,relationship 字段可以与 Join 字段结合使用,产生强大的双向关系编辑能力。如果你对双向关系感兴趣,请查看Join 字段的文档

数据保存方式

由于 relationship 字段类型存在多种配置选项,创建和更新这些字段所需的数据结构也会有所不同。以下部分将描述该字段可能产生的各种数据结构。

一对一关系

最简单的关联模式是使用 hasMany: false 并指定仅允许关联单一集合类型的 relationTo

{
  slug: 'example-collection',
  fields: [
    {
      name: 'owner', // 必填
      type: 'relationship', // 必填
      relationTo: 'users', // 必填
      hasMany: false,
    }
  ]
}

按此方式配置的字段,其文档保存数据结构如下:

{
  // 关联用户的 ObjectID
  "owner": "6031ac9e1289176380734024"
}

通过 REST API 查询该集合中的文档时,可使用如下查询方式:

?where[owner][equals]=6031ac9e1289176380734024

一对一多态关系

也称为动态引用,在此配置中,relationTo 字段是一个集合 slug 数组,用于指定 Payload 允许关联的有效集合。

{
  slug: 'example-collection',
  fields: [
    {
      name: 'owner', // 必填
      type: 'relationship', // 必填
      relationTo: ['users', 'organizations'], // 必填
      hasMany: false,
    }
  ]
}

关联多种类型时,文档保存的数据结构如下:

{
  "owner": {
    "relationTo": "organizations",
    "value": "6031ac9e1289176380734024"
  }
}

以下是按此数据查询文档的示例(注意引用 owner.value 的差异):

?where[owner.value][equals]=6031ac9e1289176380734024

您还可以查询字段关联特定集合的文档:

?where[owners.relationTo][equals]=organizations

此查询将仅返回拥有组织关联关系的文档。

多对多关系

hasMany 告诉 Payload 该字段可能保存了多个集合的关联关系。

{
  slug: 'example-collection',
  fields: [
    {
      name: 'owners', // 必填
      type: 'relationship', // 必填
      relationTo: 'users', // 必填
      hasMany: true,
    }
  ]
}

要向 hasMany 关系字段保存数据,我们需要传入一个 ID 数组:

{
  "owners": ["6031ac9e1289176380734024", "602c3c327b811235943ee12b"]
}

查询文档时,数组的查询格式保持不变:

?where[owners][equals]=6031ac9e1289176380734024

多对多 - 多态关系

{
  slug: 'example-collection',
  fields: [
    {
      name: 'owners', // 必填
      type: 'relationship', // 必填
      relationTo: ['users', 'organizations'], // 必填
      hasMany: true,
      required: true,
    }
  ]
}

hasMany 设置为关联多个集合时,关系字段会以对象数组的形式保存数据——每个对象包含集合 slug 作为 relationTo 值,以及关联文档的 id 作为 value

{
  "owners": [
    {
      "relationTo": "users",
      "value": "6031ac9e1289176380734024"
    },
    {
      "relationTo": "organizations",
      "value": "602c3c327b811235943ee12b"
    }
  ]
}

查询方式与之前的多态关系示例相同:

?where[owners.value][equals]=6031ac9e1289176380734024

查询和过滤多态关系

由于相关数据的存储方式不同,且在不同集合之间可能存在不一致性,多态关系和非多态关系的查询方式必须有所区别。因此,在 Collection List 管理界面中,对多态关系字段的过滤仅限于 id 值。

对于多态关系,响应始终是一个对象数组。每个对象都包含 relationTovalue 属性。

可以通过关联文档的 ID 来查询数据:

?where[field.value][equals]=6031ac9e1289176380734024

或者通过关联文档的 Collection slug 来查询:

?where[field.relationTo][equals]=your-collection-slug

但是,你不能查询关联文档中的任何字段值。由于我们引用了多个集合,你尝试查询的字段可能不存在,从而导致查询失败。

注意:

不能像查询非多态关系那样查询多态关系中的字段。

通过关系字段链接虚拟字段

你可以通过关系字段(或上传字段)将虚拟字段链接到其他集合中的字段,例如:

{
  collections: [
    {
      slug: 'categories',
      fields: [
        {
          name: 'title',
          type: 'text',
        },
      ],
    },
    {
      slug: 'posts',
      fields: [
        {
          type: 'relationship',
          name: 'category',
          relationTo: 'categories',
        },
        {
          type: 'text',
          name: 'categoryTitle',
          virtual: 'category.title',
        },
      ],
    },
  ],
}

在这个例子中,categoryTitle 将始终填充对应的值,即使当前 depth0。你也可以通过这个字段进行查询和排序。需要注意的是,关系字段不能是 hasMany: true 或多态的。

路径可以深度嵌套到 2 个或更多关系字段中,例如 post.category.title,只要所有关系字段都满足上述要求即可。

自定义组件

字段

服务端组件

import type React from 'react'
import { RelationshipField } from '@payloadcms/ui'
import type { RelationshipFieldServerComponent } from 'payload'

export const CustomRelationshipFieldServer: RelationshipFieldServerComponent =
  ({ clientField, path, schemaPath, permissions }) => {
    return (
      <RelationshipField
        field={clientField}
        path={path}
        schemaPath={schemaPath}
        permissions={permissions}
      />
    )
  }

客户端组件

'use client'
import React from 'react'
import { RelationshipField } from '@payloadcms/ui'
import type { RelationshipFieldClientComponent } from 'payload'

export const CustomRelationshipFieldClient: RelationshipFieldClientComponent = (
  props,
) => {
  return <RelationshipField {...props} />
}

标签

服务器组件

import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { RelationshipFieldLabelServerComponent } from 'payload'

export const CustomRelationshipFieldLabelServer: RelationshipFieldLabelServerComponent =
  (clientField, path) => {
    return (
      <FieldLabel
        label={clientField?.label || clientField?.name}
        path={path}
        required={clientField?.required}
      />
    )
  }

客户端组件

'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { RelationshipFieldLabelClientComponent } from 'payload'

export const CustomRelationshipFieldLabelClient: RelationshipFieldLabelClientComponent =
  ({ field, path }) => {
    return (
      <FieldLabel
        label={field?.label || field?.name}
        path={path}
        required={field?.required}
      />
    )
  }