obsidian/wiki/payloadcms/fields-relationship.md
2026-05-15 15:34:33 +01:00

5.7 KiB

title aliases tags sources created updated
Relationship Field
relationship-field
payload-relationship
payload-polymorphic-relationship
payloadcms
fields
relationship
polymorphic
hasMany
raw/fields__relationship.md
2026-05-15 2026-05-15

One of the most powerful Payload fields — links documents across collections, supports polymorphic (multi-collection) references, and works with the wiki/payloadcms/fields-join for bi-directional authoring.

Config Options

import type { Field } from 'payload'

export const MyRelationshipField: Field = {
  name: 'owner',
  type: 'relationship',
  relationTo: 'users',       // string or string[] for polymorphic
  hasMany: false,            // true = array of relations
}
Option Notes
relationTo * Collection slug(s) — array = polymorphic
hasMany true → array mode
minRows / maxRows Validation for hasMany
filterOptions Where query or function to limit selectable docs
maxDepth Override global populate depth for this field
unique DB-level unique index
localized Per-locale separate relation values

Admin Options

Property Notes
isSortable Drag-and-drop reorder (requires hasMany: true)
allowCreate Disable inline doc creation (default: true)
allowEdit Disable inline doc edit (default: true)
appearance 'select' (default) or 'drawer'
sortOptions String or { collectionSlug: 'fieldName' } — prefix - for desc
placeholder Custom placeholder text or function

Data Shapes

Has One (single collection)

Config: relationTo: 'users', hasMany: false

{ "owner": "6031ac9e1289176380734024" }

Query: ?where[owner][equals]=6031ac9e1289176380734024

Has One — Polymorphic (multiple collections)

Config: relationTo: ['users', 'organizations'], hasMany: false

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

Query by value: ?where[owner.value][equals]=6031ac9e1289176380734024 Query by collection: ?where[owner.relationTo][equals]=organizations

Has Many (single collection)

Config: relationTo: 'users', hasMany: true

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

Query: ?where[owners][equals]=6031ac9e1289176380734024

Has Many — Polymorphic

Config: relationTo: ['users', 'organizations'], hasMany: true

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

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

Filtering Relationship Options

filterOptions: ({ relationTo, siblingData, data, id, req, user }) => {
  if (relationTo === 'products') {
    return { stock: { greater_than: siblingData.quantity } }
  }
  return true  // no filter
}
  • Return Where query, true (no filter), or false (block all)
  • Used for both UI filtering AND server-side validation
  • Gotcha: if you also provide a custom validate function, call the default Payload validator from payload/shared inside it — otherwise filterOptions is not enforced on the backend

Bi-directional Relationships

The Relationship field alone is one-way (the related document has no back-reference). Use wiki/payloadcms/fields-join on the related collection to add a virtual reverse lookup without storing extra data.

Polymorphic Query Limitation

You cannot query on a field within a related document when the relationship is polymorphic.

Only field.value (doc ID) and field.relationTo (collection slug) are safe to query. Filtering by a sub-field (e.g. field.name) breaks because that field may not exist across all related collections.

Auto-population via Depth

Use the wiki/payloadcms/queries query param to automatically populate related documents in API responses:

  • ?depth=1 → expand one level of relationships
  • maxDepth on the field overrides the remaining depth

Custom Components

Field (Server)

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

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

Field (Client)

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

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

Key Takeaways

  • relationTo as a string array = polymorphic — data shape changes to { relationTo, value } wrapper
  • Polymorphic data is always { relationTo, value } — even for hasMany: false
  • Polymorphic querying: only field.value (ID) and field.relationTo (slug) — never sub-fields
  • hasMany: true + single collection → flat ID array; hasMany: true + polymorphic → array of { relationTo, value } objects
  • Use wiki/payloadcms/fields-join for reverse/bi-directional lookup (zero DB overhead)
  • filterOptions filters both UI dropdown and server-side validation — but only if default validator is called when a custom validate is present
  • admin.isSortable requires hasMany: true
  • admin.appearance: 'drawer' is better for large-document relationships

Sources