> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frogbot.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Relationship Field

> Relate records from one or more collections with Relationship fields.

The Relationship field connects records across collections. Use it for associations such as a product on an order, the user who placed an order, or categories assigned to a post. Collections already record the user who created and last saved each record in `createdBy` and `updatedBy`; see [Default fields](https://docs.frogbot.ai/fields/default-fields.md#document-fields).

Set `type` to `relationship` and provide the target collection slug in `relationTo`:

```ts theme={"theme":"tokyo-night"}
import type { Field } from 'frogbot'

export const ProductField: Field = {
  name: 'product',
  type: 'relationship',
  relationTo: 'products',
}
```

## Config options

| Option | Type | Default | Description |
| - | - | - | - |
| `name` | `string` | Required | Property name used in stored records and APIs. See [field names](https://docs.frogbot.ai/fields/overview.md#field-names). |
| `type` | `'relationship'` | Required | Selects the Relationship field. |
| `relationTo` | `string \| string[]` | Required | One collection slug, or an array of slugs for a polymorphic field. |
| `filterOptions` | `Where \| (args) => boolean \| Where \| Promise<boolean \| Where>` | — | Limits selectable records and validates submitted values. See [Filtering relationship options](#filtering-relationship-options). |
| `hasMany` | `boolean` | `false` | Stores an ordered array of relationships instead of one. |
| `minRows` | `number` | — | Minimum number of relationships when a value is present. Only with `hasMany: true`. |
| `maxRows` | `number` | — | Maximum number of relationships when a value is present. Only with `hasMany: true`. |
| `maxDepth` | `number` | Request depth | Maximum population depth for this field, regardless of the remaining request depth. See [depth](https://docs.frogbot.ai/queries/depth.md#max-depth). |
| `graphQL` | `{ complexity?: number }` | `{ complexity: 10 }` | Query complexity used only when an application mounts its own GraphQL executor. FrogBot does not ship a GraphQL request handler. |
| `label` | `string \| Record<string, string> \| (({ i18n, t }) => string) \| false` | Generated from `name` | Label shown in the admin panel. Use an object keyed by language code for translations, or `false` to hide the label. |
| `required` | `boolean` | `false` | Rejects an empty value in the default validation. |
| `unique` | `boolean` | `false` | Creates a unique database index for the field path. See [indexes](https://docs.frogbot.ai/database/indexes.md). |
| `index` | `boolean` | `false` | Creates a database index for faster queries. `unique` already creates one. |
| `localized` | `boolean` | `false` | Stores one value per configured locale. Ignored without [localization](https://docs.frogbot.ai/configuration/localization.md) or inside a localized parent. |
| `defaultValue` | `string \| number \| { relationTo, value }`, an array of these, or `({ locale, req, user }) => value` | — | Value used when the field has no value. See [default values](https://docs.frogbot.ai/fields/overview.md#default-values). |
| `validate` | `Validate` | Built-in validator | Replaces the built-in validator. See [validation](https://docs.frogbot.ai/fields/overview.md#validation). |
| `hooks` | `{ beforeValidate, beforeChange, afterChange, afterRead, beforeDuplicate }` | — | Arrays of `FieldHook` functions for each phase. See [field hooks](https://docs.frogbot.ai/hooks/fields.md). |
| `access` | `{ create, read, update }` | Allowed | `FieldAccess` functions for each operation. See [field access control](https://docs.frogbot.ai/access-control/fields.md). |
| `hidden` | `boolean` | `false` | Removes the field from API responses and the admin panel while keeping stored data. |
| `saveToJWT` | `boolean \| string` | `false` | In an auth-enabled collection, copies the value into the user's token. A string sets the token key. See [token data](https://docs.frogbot.ai/authentication/token-data.md). |
| `virtual` | `boolean \| string` | `false` | `true` skips database storage; a string reads a value through a relationship path. Virtual fields are read-only in the admin panel unless `admin.readOnly` is `false`. See [Linking virtual fields with relationships](#linking-virtual-fields-with-relationships). |
| `disableDuplicate` | `boolean` | `false` | Leaves the value out when a document is duplicated, so the copy uses `defaultValue`. |
| `admin` | `object` | — | Admin-panel options. See [Admin options](#admin-options). |
| `custom` | `Record<string, any>` | — | Server-only metadata for your own code and plugins. Use `admin.custom` for data the browser also needs. |
| `jsonSchema` | `Array<({ jsonSchema }) => JSONSchema4>` | — | Functions that modify the JSON Schema used by [type generation](https://docs.frogbot.ai/typescript/generating-types.md). |

Use the [depth parameter](https://docs.frogbot.ai/queries/depth.md) to populate related records in API
responses.

## Admin options

The `admin` option inherits the standard [field admin options](https://docs.frogbot.ai/fields/overview.md#admin-options) and adds settings specific to relationships:

| Option | Type | Default | Description |
| - | - | - | - |
| `appearance` | `'select' \| 'drawer'` | `'select'` | `select` shows a searchable dropdown; `drawer` opens a list drawer to pick records. |
| `allowCreate` | `boolean` | `true` | Lets editors create a related record from the field. |
| `allowEdit` | `boolean` | `true` | Lets editors edit a selected record from the field. |
| `isSortable` | `boolean` | `true` | Lets editors reorder values when `hasMany` is `true`. |
| `sortOptions` | `string \| Record<string, string>` | — | Sort for records in the selector: one field name, or one per collection for a polymorphic field. See [Sort options](#sort-options). |
| `placeholder` | `string \| (({ i18n, t }) => string)` | — | Placeholder text shown when nothing is selected. |

### Sort options

Provide a field name to sort every target collection the same way. Prefix it with `-` for descending order:

```ts theme={"theme":"tokyo-night"}
const field: Field = {
  name: 'category',
  type: 'relationship',
  relationTo: 'categories',
  admin: {
    sortOptions: '-name',
  },
}
```

For a polymorphic field, provide a value for each collection:

```ts theme={"theme":"tokyo-night"}
const field: Field = {
  name: 'relatedContent',
  type: 'relationship',
  relationTo: ['pages', 'posts', 'categories'],
  admin: {
    sortOptions: {
      pages: 'title',
      posts: '-publishedAt',
      categories: 'name',
    },
  },
}
```

Without `sortOptions`, the selector uses its default ordering.

## Filtering relationship options

Use `filterOptions` to limit records in the selector and validate submitted relationships. It accepts a [query constraint](https://docs.frogbot.ai/queries/overview.md), or a callback that returns a constraint, `true` to allow every record, or `false` to allow none.

The callback receives:

| Option | Type | Default | Description |
| - | - | - | - |
| `blockData` | `any` | — | Data for the nearest parent block. It is `undefined` outside a block and in a list-view filter. |
| `data` | `any` | — | Full data for the record being edited. It is an empty object in a list-view filter. |
| `id` | `string \| number` | — | Current record ID, or `undefined` during creation and in a list-view filter. |
| `relationTo` | `string` | — | Collection slug currently being filtered. |
| `req` | `FrogBotRequest` | — | Current request, including `req.frogbot`, `req.user`, `req.locale`, and request context. |
| `siblingData` | `unknown` | — | Data from fields under the same parent. It is an empty object in a list-view filter. |
| `user` | `Partial<User> \| null` | — | Current authenticated user. |

The callback can return a different constraint for each target collection:

```ts theme={"theme":"tokyo-night"}
import type { CollectionConfig } from 'frogbot'

export const Orders: CollectionConfig = {
  slug: 'orders',
  fields: [
    {
      name: 'quantity',
      type: 'number',
      required: true,
    },
    {
      name: 'purchase',
      type: 'relationship',
      relationTo: ['products', 'services'],
      filterOptions: ({ relationTo, siblingData }) => {
        if (relationTo === 'products') {
          return {
            stock: { greater_than: siblingData.quantity },
          }
        }

        return {
          isAvailable: { equals: true },
        }
      },
    },
  ],
}
```

When combining `filterOptions` with custom validation, make the validation
enforce every constraint your application requires. The selector filter alone
is not a replacement for custom validation logic.

## Bidirectional relationships

A Relationship field stores the connection on the record containing it. The related record does not automatically receive a reverse list.

Add a [Join field](https://docs.frogbot.ai/fields/join.md) to the related collection when editors or API consumers need to traverse the relationship in both directions.

## Stored data

The stored shape depends on whether the field accepts one or many records and one or many collection types.

### Has one

A relationship to one collection stores the related record ID:

```ts theme={"theme":"tokyo-night"}
import type { CollectionConfig } from 'frogbot'

export const Orders: CollectionConfig = {
  slug: 'orders',
  fields: [
    {
      name: 'owner',
      type: 'relationship',
      relationTo: 'users',
    },
  ],
}
```

```json theme={"theme":"tokyo-night"}
{
  "owner": "6031ac9e1289176380734024"
}
```

Query it through the field name:

```text theme={"theme":"tokyo-night"}
?where[owner][equals]=6031ac9e1289176380734024
```

### Has one, polymorphic

An array in `relationTo` allows one record from any listed collection:

```ts theme={"theme":"tokyo-night"}
import type { CollectionConfig } from 'frogbot'

export const Orders: CollectionConfig = {
  slug: 'orders',
  fields: [
    {
      name: 'owner',
      type: 'relationship',
      relationTo: ['users', 'organizations'],
    },
  ],
}
```

The value records both the collection and ID:

```json theme={"theme":"tokyo-night"}
{
  "owner": {
    "relationTo": "organizations",
    "value": "6031ac9e1289176380734024"
  }
}
```

Query the ID through `value`, or query the target collection through `relationTo`:

```text theme={"theme":"tokyo-night"}
?where[owner.value][equals]=6031ac9e1289176380734024
```

```text theme={"theme":"tokyo-night"}
?where[owner.relationTo][equals]=organizations
```

### Has many

Set `hasMany` to store an array of IDs from one collection:

```ts theme={"theme":"tokyo-night"}
import type { CollectionConfig } from 'frogbot'

export const Projects: CollectionConfig = {
  slug: 'projects',
  fields: [
    {
      name: 'owners',
      type: 'relationship',
      relationTo: 'users',
      hasMany: true,
    },
  ],
}
```

```json theme={"theme":"tokyo-night"}
{
  "owners": ["6031ac9e1289176380734024", "602c3c327b811235943ee12b"]
}
```

Querying an ID uses the same field path:

```text theme={"theme":"tokyo-night"}
?where[owners][equals]=6031ac9e1289176380734024
```

### Has many, polymorphic

Combine an array in `relationTo` with `hasMany`:

```ts theme={"theme":"tokyo-night"}
import type { CollectionConfig } from 'frogbot'

export const Projects: CollectionConfig = {
  slug: 'projects',
  fields: [
    {
      name: 'owners',
      type: 'relationship',
      relationTo: ['users', 'organizations'],
      hasMany: true,
      required: true,
    },
  ],
}
```

```json theme={"theme":"tokyo-night"}
{
  "owners": [
    {
      "relationTo": "users",
      "value": "6031ac9e1289176380734024"
    },
    {
      "relationTo": "organizations",
      "value": "602c3c327b811235943ee12b"
    }
  ]
}
```

Querying the related ID uses the same `.value` path as a polymorphic has-one field:

```text theme={"theme":"tokyo-night"}
?where[owners.value][equals]=6031ac9e1289176380734024
```

### Querying and filtering polymorphic relationships

Polymorphic values always identify both the target collection in `relationTo` and the related record in `value`. Their storage differs from non-polymorphic relationships, so list-view filtering is limited to the related ID.

Filter by related record ID:

```text theme={"theme":"tokyo-night"}
?where[owners.value][equals]=6031ac9e1289176380734024
```

Or filter by target collection slug:

```text theme={"theme":"tokyo-night"}
?where[owners.relationTo][equals]=organizations
```

You cannot query arbitrary fields inside records reached through a polymorphic
relationship. A field may not exist in every target collection.

## Linking virtual fields with relationships

A virtual field can expose data from a populated relationship without storing another database value. Set `virtual` to the relationship path that leads to the value.

```ts theme={"theme":"tokyo-night"}
import type { CollectionConfig } from 'frogbot'

export const Orders: CollectionConfig = {
  slug: 'orders',
  fields: [
    {
      name: 'customer',
      type: 'relationship',
      relationTo: 'customers',
      required: true,
    },
    {
      name: 'customerName',
      type: 'text',
      virtual: 'customer.name',
    },
  ],
}
```

The path starts at a relationship field on the same record. FrogBot resolves the linked value when the relationship is populated. The virtual field remains absent from database storage, so use the underlying relationship path when writing data.

## Custom components

Override the field or its label through `admin.components`. Custom components can wrap FrogBot's default relationship controls.

### Field

#### Server component

```tsx theme={"theme":"tokyo-night"}
import { RelationshipField } from '@frogbotai/ui/admin'
import type { RelationshipFieldServerProps } from 'frogbot'

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

#### Client component

```tsx theme={"theme":"tokyo-night"}
'use client'

import { RelationshipField } from '@frogbotai/ui/admin'
import type { RelationshipFieldClientProps } from 'frogbot'

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

### Label

#### Server component

```tsx theme={"theme":"tokyo-night"}
import { FieldLabel } from '@frogbotai/ui/admin'
import type { RelationshipFieldLabelServerProps } from 'frogbot'

export const CustomRelationshipLabelServer = ({
  clientField,
  path,
}: RelationshipFieldLabelServerProps) => (
  <FieldLabel
    label={clientField.label || clientField.name}
    path={path}
    required={clientField.required}
  />
)
```

#### Client component

```tsx theme={"theme":"tokyo-night"}
'use client'

import { FieldLabel } from '@frogbotai/ui/admin'
import type { RelationshipFieldLabelClientProps } from 'frogbot'

export const CustomRelationshipLabelClient = ({
  field,
  path,
}: RelationshipFieldLabelClientProps) => (
  <FieldLabel
    label={field.label || field.name}
    path={path}
    required={field.required}
  />
)
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.