168 lines
5.7 KiB
Markdown
168 lines
5.7 KiB
Markdown
---
|
|
title: "Relationship Field"
|
|
aliases: [relationship-field, payload-relationship, payload-polymorphic-relationship]
|
|
tags: [payloadcms, fields, relationship, polymorphic, hasMany]
|
|
sources: [raw/fields__relationship.md]
|
|
created: 2026-05-15
|
|
updated: 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|Join Field]] for bi-directional authoring.
|
|
|
|
## Config Options
|
|
|
|
```ts
|
|
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`
|
|
|
|
```json
|
|
{ "owner": "6031ac9e1289176380734024" }
|
|
```
|
|
|
|
Query: `?where[owner][equals]=6031ac9e1289176380734024`
|
|
|
|
### Has One — Polymorphic (multiple collections)
|
|
|
|
Config: `relationTo: ['users', 'organizations']`, `hasMany: false`
|
|
|
|
```json
|
|
{
|
|
"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`
|
|
|
|
```json
|
|
{ "owners": ["6031ac9e1289176380734024", "602c3c327b811235943ee12b"] }
|
|
```
|
|
|
|
Query: `?where[owners][equals]=6031ac9e1289176380734024`
|
|
|
|
### Has Many — Polymorphic
|
|
|
|
Config: `relationTo: ['users', 'organizations']`, `hasMany: true`
|
|
|
|
```json
|
|
{
|
|
"owners": [
|
|
{ "relationTo": "users", "value": "6031ac9e1289176380734024" },
|
|
{ "relationTo": "organizations", "value": "602c3c327b811235943ee12b" }
|
|
]
|
|
}
|
|
```
|
|
|
|
Query: `?where[owners.value][equals]=6031ac9e1289176380734024`
|
|
|
|
## Filtering Relationship Options
|
|
|
|
```ts
|
|
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|Join Field]] 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|Depth]] 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)
|
|
```tsx
|
|
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)
|
|
```tsx
|
|
'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|Join Field]] 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
|
|
|
|
- `raw/fields__relationship.md` — https://payloadcms.com/docs/fields/relationship
|