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

# Join Field

> Surface records that refer to the current record without storing a second relationship.

The Join field exposes a relationship or upload field in the opposite direction. It is virtual: FrogBot queries the related collection instead of storing a second list of IDs on the current record.

Use a Join field to show a product's orders, a category's posts, or every record that uses an uploaded file.

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

export const Categories: CollectionConfig = {
  slug: 'categories',
  fields: [
    {
      name: 'relatedPosts',
      type: 'join',
      collection: 'posts',
      on: 'category',
    },
  ],
}

export const Posts: CollectionConfig = {
  slug: 'posts',
  fields: [
    {
      name: 'category',
      type: 'relationship',
      relationTo: 'categories',
    },
  ],
}
```

Here, `relatedPosts` finds records whose `category` field refers to the current category. `on` also accepts a nested path such as `details.category`.

Join fields are populated when query depth is at least `1`. In the Local API,
set `joins: false` when a query does not need reverse relationships.

## Schema design

Store a relationship in one place whenever possible. A post can store its category while the category uses a Join field to retrieve its posts. This avoids synchronizing duplicate relationship arrays and remains practical when one category has many posts.

For relationships with their own data, create a junction collection. For example, a `category-posts` collection can contain `category`, `post`, `featured`, and `position` fields. Join fields on both related collections can then surface those records.

On PostgreSQL and SQLite, polymorphic or many-valued relationships normally use an automatically managed `_rels` table. A dedicated junction collection gives you direct control over that table's fields and indexes. It can be hidden from admin navigation while Join fields provide the editing interface on either side.

## 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` | `'join'` | Required | Selects the Join field. |
| `collection` | `string \| string[]` | Required | Slug of the collection to query, or an array of slugs for a polymorphic join. |
| `on` | `string` | Required | Path of the relationship or upload field, in `collection`, that points to the current collection. FrogBot indexes that field if it is not already indexed. |
| `where` | `Where` | — | Base filter merged with a query's join filter. |
| `defaultLimit` | `number` | `10` | Number of related records returned when a query sets no limit. Use `0` for all records. |
| `defaultSort` | `Sort` | Collection default sort | Default sort for related records. With `orderable`, defaults to the generated order field. |
| `maxDepth` | `number` | `1` | Maximum population depth below this field. |
| `orderable` | `boolean` | `false` | Experimental. Lets editors reorder joined records by drag and drop, using a generated order field on the related collection. Requires a single `collection`. |
| `hasMany` | `boolean` | From the `on` field | Set automatically from the `on` field. A configured value is replaced. |
| `localized` | `boolean` | From the `on` field | Set automatically from the `on` field. A configured value is replaced. |
| `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. |
| `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 | Only `read` has an effect: Join values are never written. See [field access control](https://docs.frogbot.ai/access-control/fields.md). |
| `hidden` | `false` | `false` | Join fields can't be hidden. Use `access.read` or `admin.hidden` instead. |
| `required` | `boolean` | `false` | No effect: Join fields have no built-in validator and their values are never written. |
| `disableDuplicate` | `boolean` | `false` | No effect: Join values are not stored, so there is nothing to copy. |
| `unique` | `boolean` | `false` | No effect: Join values are not stored, so there is nothing to index. |
| `validate` | `Validate` | — | No effect: Join values are never written. |
| `virtual` | `boolean \| string` | `false` | No effect: Join fields are never stored. |
| `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). |
| `sanitizedMany` | `JoinField[]` | — | Internal. Set by FrogBot for polymorphic joins; don't set it yourself. |
| `index` | `undefined` | — | Not supported. |
| `defaultValue` | `undefined` | — | Not supported. |
| `graphQL` | `{ complexity?: number }` | `{ complexity: 10 }` | Query complexity used only when an application mounts its own GraphQL executor. FrogBot does not ship a GraphQL endpoint. |
| `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). |

### Admin options

Join fields support the standard [field admin options](https://docs.frogbot.ai/fields/overview.md#admin-options) except `disableBulkEdit` and `readOnly`, plus:

| Option | Type | Default | Description |
| - | - | - | - |
| `defaultColumns` | `string[]` | Related collection's default columns | Fields shown in the related-record table. |
| `allowCreate` | `boolean` | `true` | Lets editors create a related record from the field. Only shown on saved documents. |
| `disableRowTypes` | `boolean` | `false` for polymorphic joins | Hides the collection label on each row. Labels appear only for polymorphic joins by default. |
| `disableBulkEdit` | `undefined` | — | Not supported. Join fields are never bulk edited. |
| `readOnly` | `undefined` | — | Not supported. Join fields have no editable value. |

## Returned data

A populated Join field returns `docs`, `hasNextPage`, and, when `count: true` is requested, `totalDocs`:

```json theme={"theme":"tokyo-night"}
{
  "relatedPosts": {
    "docs": [{ "id": "post-1", "title": "First post" }],
    "hasNextPage": false,
    "totalDocs": 1
  }
}
```

For a polymorphic Join, each item contains `relationTo` and `value`.

```json theme={"theme":"tokyo-night"}
{
  "relatedContent": {
    "docs": [
      {
        "relationTo": "posts",
        "value": { "id": "post-1", "title": "First post" }
      }
    ],
    "hasNextPage": false,
    "totalDocs": 1
  }
}
```

## Query options

`find` and `findByID` accept a `joins` option. Pass `false` to leave every Join field out of the result, or configure each Join field by name:

| Option | Type | Default | Description |
| - | - | - | - |
| `count` | `boolean` | `false` | Include `totalDocs`, the number of related documents. |
| `limit` | `number` | Field `defaultLimit` (`10`) | The maximum number of related documents to return. `0` returns all of them. |
| `page` | `number` | `1` | The page of related documents to return. |
| `sort` | `string` | Field `defaultSort` | Order related documents by a field. Prefix it with `-` for descending order. |
| `where` | `Where` | — | A [query](https://docs.frogbot.ai/queries/overview.md) that filters related documents. It's combined with the field's own `where`. |

Set a field to `false` instead of an object to leave just that field out.

```ts theme={"theme":"tokyo-night"}
const result = await frogbot.find({
  collection: 'categories',
  overrideAccess: true,
  joins: {
    relatedPosts: {
      count: true,
      limit: 5,
      sort: '-createdAt',
      where: {
        published: { equals: true },
      },
    },
  },
})

const withoutJoins = await frogbot.findByID({
  collection: 'categories',
  overrideAccess: true,
  id: 'category-1',
  joins: false,
})
```

Filtering polymorphic joins is limited for relationship fields nested inside
arrays and blocks.

## REST API

The REST API accepts the same options through a `joins` query parameter:

```text theme={"theme":"tokyo-night"}
/api/categories/category-1?joins[relatedPosts][limit]=5&joins[relatedPosts][sort]=-createdAt&joins[relatedPosts][count]=true
```

Repeat the nested parameters to configure additional Join fields or filters.

To leave a Join field out of the result, set it to `false`, such as `?joins[relatedPosts]=false`. The REST API reads `joins` one field at a time, so `?joins=false` has no effect: list each Join field instead.

## GraphQL

Join fields retain GraphQL schema metadata for applications that provide and verify their own GraphQL execution integration. FrogBot does not currently ship or mount a public GraphQL endpoint, so this page does not claim an executable GraphQL query path. Use the Local or REST API examples above in a standard FrogBot application.


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