---
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
