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

# Fields Overview

> Configure FrogBot fields, validation, defaults, access, hooks, and custom admin components.

Fields define the shape of Documents stored in a [Collection](https://docs.frogbot.ai/configuration/collections.md). FrogBot uses the same definitions to build database schemas, API responses, generated TypeScript types, and the editing interface in the admin panel.

Most fields store data. Layout fields organize the editing interface without adding values to a Document, while virtual fields expose values that are not persisted.

> [!NOTE]
>
> FrogBot also contributes fields to collections with framework roles. See [role-marked collections](#role-marked-collections) and [Default Fields](https://docs.frogbot.ai/fields/default-fields.md).

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

export const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
  ],
}
```

## Field types

Choose the field type that matches the value and editing experience you need.

### Data fields

Data fields have a `name` and ordinarily store their value in the database.

* [Array](https://docs.frogbot.ai/fields/array.md) for repeating rows of nested fields
* [Blocks](https://docs.frogbot.ai/fields/blocks.md) for block-based content
* [Checkbox](https://docs.frogbot.ai/fields/checkbox.md) for boolean values
* [Code](https://docs.frogbot.ai/fields/code.md) for source code stored as a string
* [Date](https://docs.frogbot.ai/fields/date.md) for date and time values
* [Email](https://docs.frogbot.ai/fields/email.md) for validated email addresses
* [Group](https://docs.frogbot.ai/fields/group.md) for fields nested in a keyed object
* [JSON](https://docs.frogbot.ai/fields/json.md) for structured JSON values
* [Number](https://docs.frogbot.ai/fields/number.md) for numeric values
* [Point](https://docs.frogbot.ai/fields/point.md) for geographic coordinates
* [Radio](https://docs.frogbot.ai/fields/radio.md) for one value from a visible set of choices
* [Relationship](https://docs.frogbot.ai/fields/relationship.md) for references to other collections
* [Rich Text](https://docs.frogbot.ai/fields/rich-text.md) for structured rich content
* [Select](https://docs.frogbot.ai/fields/select.md) for one or more values from a choice list
* [Tabs](https://docs.frogbot.ai/fields/tabs.md) for named groups displayed as tabs
* [Text](https://docs.frogbot.ai/fields/text.md) for single-line strings
* [Textarea](https://docs.frogbot.ai/fields/textarea.md) for multi-line strings
* [Upload](https://docs.frogbot.ai/fields/upload.md) for references to file collections

### Layout fields

Layout fields organize the admin form and do not store their own value.

* [Collapsible](https://docs.frogbot.ai/fields/collapsible.md) groups fields in an expandable section.
* [Row](https://docs.frogbot.ai/fields/row.md) arranges fields horizontally.
* Unnamed [Groups](https://docs.frogbot.ai/fields/group.md) and unnamed [Tabs](https://docs.frogbot.ai/fields/tabs.md) arrange nested fields without adding another key to the Document.
* [UI](https://docs.frogbot.ai/fields/ui.md) renders a custom interface without storing data.

### Virtual fields

[Join fields](https://docs.frogbot.ai/fields/join.md) are virtual by design. Other data fields can use `virtual: true` and an `afterRead` hook to return a computed value without persisting it.

> [!TIP]
>
> If no built-in field type fits your application, combine [validation](#validation) with a [custom field component](#custom-components) to create the required editing experience while keeping an appropriate underlying data type.

## Virtual field configuration

Any data field can be made virtual by setting its `virtual` property. Virtual values appear in API responses and can be displayed in the admin panel, but FrogBot does not persist them in the database. They are useful for computed values, relationship summaries, and formatted versions of stored data.

The `virtual` property accepts either `true` or a string relationship path.

### Boolean virtual fields

Setting `virtual: true` marks the field as virtual but does not populate it. Use an `afterRead` field hook to calculate its value.

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

export const FullName: Field = {
  name: 'fullName',
  type: 'text',
  virtual: true,
  hooks: {
    afterRead: [({ siblingData }) => `${siblingData.firstName} ${siblingData.lastName}`],
  },
}
```

### String path virtual fields

A string `virtual` value creates a virtual relationship field. FrogBot follows the path from a relationship in the current Document and returns the value at the destination.

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

export const AuthorName: Field = {
  name: 'authorName',
  type: 'text',
  virtual: 'author.name',
}
```

#### Display in the admin

In List, Board and Calendar, a string path virtual field shows its value the way the field at the end of the path shows it. A path to a [money](https://docs.frogbot.ai/fields/money.md) field shows a money amount, a path to a select or radio with [option colors](https://docs.frogbot.ai/fields/select.md#option-colors) shows the coloured pill, and a path to a date uses that date field's display format. An empty value shows what the source field shows when it is empty, so an empty money amount shows nothing, and `0` shows as zero (`$0.00`).

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

export const Projects: CollectionConfig = {
  slug: 'projects',
  fields: [{ name: 'name', type: 'text' }, moneyField({ name: 'budget', currency: 'USD' })],
}

export const Tasks: CollectionConfig = {
  slug: 'tasks',
  fields: [
    { name: 'project', type: 'relationship', relationTo: 'projects' },
    { name: 'tags', type: 'relationship', relationTo: 'tags', hasMany: true },
    { name: 'projectBudget', type: 'number', virtual: 'project.budget' },
    { name: 'tagNames', type: 'text', virtual: 'tags.name' },
  ],
}
```

A task whose project has a budget of `12.5` shows `$12.50` in the Project Budget column, and a task tagged "urgent" and "backend" shows `urgent, backend`.

* **Sources.** The field at the end of the path can be a text, textarea, email, number, select, radio, checkbox or date field, including fields built on them such as [money](https://docs.frogbot.ai/fields/money.md). The path can cross several relationships, named groups and named tabs.
* **Lists.** When the path crosses a `hasMany` relationship, each non-empty value is shown with the source field's display, separated by commas. In the List, only the first value links to the Document.
* **Type and options.** Give the virtual field the source field's `type`, `options` and `hasMany`. The display follows the source field, but filters and generated types follow what you write on the virtual field.
* **Paths that can't be followed.** If the path names a field that doesn't exist, passes through an array or blocks field, crosses a relationship to more than one collection, or ends at an unsupported or virtual field, the field shows as a plain value. A misspelt path returns an empty value and doesn't stop FrogBot from starting.
* **Your own display.** A [custom `Cell`](#cell) on the virtual field, or a field factory such as `moneyField()` used for the virtual field itself, is used instead of the source field's display.
* **Edit view.** The edit view shows the virtual field's own read-only input for the type you wrote. For example, a number virtual field pointing at a money field shows `12.5`.

Grouping a Board by a virtual path field, or using one as a Calendar date, isn't supported.

### Virtual path syntax

Virtual paths use dot notation to traverse relationships and nested data:

* `author.name` returns the `name` field from the `author` relationship.
* `author.profile.bio` returns `bio` from the related author's nested `profile` object.
* `categories.title` returns the `title` values from a `hasMany` relationship.
* `request.additionalStakeholders.email` traverses multiple relationship levels.

The first path segment must name a relationship field in the same collection. Paths resolve while FrogBot reads the Document. When a path traverses a `hasMany` relationship, the virtual field returns an array of values.

### Common use cases

#### Displaying relationship names

Add a virtual field when an API response should expose a useful property next to a relationship ID:

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

export const AuthorFields: Field[] = [
  {
    name: 'author',
    type: 'relationship',
    relationTo: 'users',
  },
  {
    name: 'authorName',
    type: 'text',
    virtual: 'author.name',
  },
]
```

#### Multiple relationship values

Virtual paths through `hasMany` relationships return arrays:

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

export const CategoryFields: Field[] = [
  {
    name: 'categories',
    type: 'relationship',
    relationTo: 'categories',
    hasMany: true,
  },
  {
    name: 'categoryTitles',
    type: 'text',
    virtual: 'categories.title',
  },
]
```

#### Computed values

Use a hook when the result is calculated rather than read from a relationship:

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

export const WordCount: Field = {
  name: 'wordCount',
  type: 'number',
  virtual: true,
  hooks: {
    afterRead: [({ siblingData }) => {
      const content = siblingData.content || ''

      return content.split(/\s+/).filter(Boolean).length
    }],
  },
}
```

### Virtual fields in API responses

Virtual fields appear alongside stored fields in API responses:

```json theme={"theme":"tokyo-night"}
{
  "id": "123",
  "title": "My Post",
  "author": "64f1234567890abcdef12345",
  "authorName": "John Doe",
  "categories": ["64f9876543210fedcba67890", "64f5432109876543210abcdef"],
  "categoryTitles": ["Tech", "News"],
  "wordCount": 450
}
```

> [!WARNING]
>
> A string virtual path cannot resolve unless its source relationship exists in the same collection. For example, `virtual: 'author.name'` requires an `author` relationship field.

## Field options

Every field requires a `type`. Data fields also require a `name`. Each field type adds options for its value, storage, validation, and editing interface.

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

export const Subtitle: Field = {
  name: 'subtitle',
  type: 'text',
  maxLength: 120,
}
```

See the page for each [field type](#field-types) for its complete options.

> [!WARNING]
>
> A field is an object with at least a `type`. The selected type determines the other accepted options, API value shape, and default admin component.

### Field names

Field names become keys in API response objects and generated TypeScript types. Names must be unique among sibling fields. Use JavaScript-style identifiers that begin with a letter or underscore and contain only letters, numbers, and underscores.

> [!WARNING]
>
> Avoid names beginning with a digit, such as `1field`, or containing hyphens, such as `field-name`. Although these are valid string values, they can conflict with dot-notation paths, database columns, and schema identifiers.

FrogBot reserves internal names. A reserved field is removed while the configuration is sanitized. The following names cannot be used:

* `__v`
* `salt`
* `hash`
* `file`
* `status` when drafts and the Postgres adapter are enabled

Enabled FrogBot features can reserve additional names for the fields they manage. See [Default Fields](https://docs.frogbot.ai/fields/default-fields.md) for those conditional fields.

### Field-level hooks

Field hooks run at specific points while FrogBot creates, updates, or reads a Document.

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

export const NormalizedTitle: Field = {
  name: 'title',
  type: 'text',
  hooks: {
    beforeValidate: [({ value }) => value?.trim()],
  },
}
```

Hook arguments include a `FrogBotRequest`. Server hooks access the current framework instance through `req.frogbot`, not through a separate global or per-field instance.

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

export const CollectionCount: Field = {
  name: 'collectionCount',
  type: 'number',
  virtual: true,
  hooks: {
    afterRead: [({ req }) => Object.keys(req.frogbot.collections).length],
  },
}
```

See [Field Hooks](https://docs.frogbot.ai/hooks/fields.md) for hook order and all arguments.

### Field-level access control

Field access functions control whether a field can be created, updated, or read. They return a boolean and receive the current `req`, Document data, and sibling data.

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

export const InternalNotes: Field = {
  name: 'internalNotes',
  type: 'textarea',
  access: {
    read: ({ req }) => Boolean(req.user),
    update: ({ req }) => Boolean(req.user),
  },
}
```

See [Field Access Control](https://docs.frogbot.ai/access-control/fields.md) for the operation-specific rules.

### Default values

Use `defaultValue` to populate a missing value during create or update operations and when the Admin Panel opens a create form. It can be a static value or a function based on the current locale or authenticated user.

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

export const Greeting: Field = {
  name: 'greeting',
  type: 'text',
  defaultValue: 'Hello, World!',
}
```

A default-value function receives:

| Property | Type | Description |
| - | - | - |
| `user` | `User \| null` | The authenticated user, or `null`. |
| `locale` | `string \| null \| undefined` | The currently selected locale. |
| `req` | `FrogBotRequest` | The current request. Server code accesses the instance through `req.frogbot`, which is attached even when the Admin Panel builds a form. |

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

const attribution = {
  en: 'Written by',
  es: 'Escrito por',
}

export const Attribution: Field = {
  name: 'attribution',
  type: 'text',
  defaultValue: ({ locale, user }) => {
    const language = locale === 'es' ? 'es' : 'en'
    const author = user?.name ?? 'Unknown'

    return `${attribution[language]} ${author}`
  },
}
```

Static defaults may also be reflected in the database schema by the configured database adapter. Defaults do not replace validation or access control.

> [!TIP]
>
> A default-value function can use `req.frogbot` to call the Local API on the server. Keep the function deterministic enough for every create or update path that can omit the field.

### Validation

FrogBot automatically validates field types and constraints such as `required`, `min`, `max`, and length limits. Set `validate` to replace a field's default validator with application-specific logic.

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

export const ProductCode: Field = {
  name: 'productCode',
  type: 'text',
  validate: (value) => {
    if (!value) return 'A product code is required'
    if (!/^[A-Z]{3}-\d{4}$/.test(value)) return 'Use the format ABC-1234'

    return true
  },
}
```

A validator returns `true` when the value is valid or an error-message string when it is invalid. It may also return a promise. The context includes:

| Argument | Type | Description |
| - | - | - |
| `value` | `TValue \| null \| undefined` | The field value being validated. |
| `ctx` | `ValidateOptions` | Document, request, operation, and field-path context, described below. |

#### Validation context

Use the context to compare sibling values, inspect the operation, identify the current Document, or read the authenticated user from `req.user`.

| Property | Type | Description |
| - | - | - |
| `blockData` | `Partial<TData>` | The nearest parent block's data, or `undefined` outside a block. |
| `collectionSlug` | `string \| undefined` | The Collection slug, when available. |
| `data` | `Partial<TData>` | The top-level Document data for the operation; it may omit unchanged fields. |
| `siblingData` | `Partial<TSiblingData>` | Data under the same parent as the field. |
| `operation` | `'create' \| 'read' \| 'update' \| 'delete' \| undefined` | The current operation, when available. |
| `overrideAccess` | `boolean \| undefined` | Whether field Access Control is bypassed. |
| `path` | `(string \| number)[]` | The runtime path, including array indexes, such as `['items', 0, 'title']`. |
| `id` | `string \| number \| undefined` | The current Document ID, or `undefined` during create. |
| `preferences` | `DocumentPreferences` | The Document's saved admin preferences. |
| `previousValue` | `TValue \| undefined` | The previous field value, when available. |
| `req` | `FrogBotRequest` | The current request; on the server, the authenticated user is `req.user` and the instance is `req.frogbot`. |
| `event` | `'onChange' \| 'submit' \| undefined` | Which form event triggered validation, allowing expensive checks to run only on submission. |
| `required` | `boolean \| undefined` | The field's `required` setting. |

The context also includes the field's own settings as top-level properties, such as `minLength` on a Text field or `max` on a Number field. These settings are typed for that field type and exclude `hooks`, `access`, and `validate`.

`operation` can be any of the four operations or `undefined`, so a validator that switches on it should handle `read` and `delete` too.

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

export const MemberOnlyValue: Field = {
  name: 'memberOnlyValue',
  type: 'text',
  validate: (value, { req }) => Boolean(req.user) || 'You must be signed in to save this field',
}
```

#### Localized error messages

Validation can use the request translation function to return a localized built-in message:

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

export const RequiredTitle: Field = {
  name: 'title',
  type: 'text',
  validate: (value, { req: { t } }) => Boolean(value) || t('validation:required'),
}
```

You can also return application-specific translated strings configured through [internationalization](https://docs.frogbot.ai/configuration/i18n.md).

#### Replacing default field validation

When a field defines `validate`, FrogBot uses that function instead of the field type's default validator. FrogBot does not expose the built-in validators through a supported public helper import. Include every type, required, length, range, or format rule that the replacement must retain.

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

export const Rating: Field = {
  name: 'rating',
  type: 'number',
  validate: (value) => {
    if (typeof value !== 'number') return 'A numeric rating is required'
    if (value < 1 || value > 5) return 'The rating must be between 1 and 5'

    return true
  },
}
```

#### Validation performance

The admin panel runs validation whenever a field changes. Keep synchronous and `onChange` checks lightweight. For expensive work such as a remote lookup, use `event` to defer it until form submission.

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

export const AccountCode: Field = {
  name: 'accountCode',
  type: 'text',
  validate: async (value, { event, req }) => {
    if (!req.user) return 'You must be signed in'
    if (event === 'onChange') return true

    const response = await fetch(`https://example.com/accounts/${value}`)

    return response.ok || 'The account code was not found'
  },
}
```

Validation also runs in the browser for admin form feedback. Browser validation receives form context, but it is not a server-instance API: do not use `req.frogbot` for client-side database work. Enforce authoritative rules on the server and use hooks or access control when the check requires framework operations.

> [!TIP]
>
> Validation messages are returned by API operations as well as displayed in the admin panel. Write them so they explain how to correct the value.

## Role-marked collections

FrogBot can merge managed fields into a collection marked with `chat: true`, `message: true`, `file: true`, or `usageLog: true`. A collection can have only one of these role markers, and only one collection can fill each role.

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

export const Conversations: CollectionConfig = {
  slug: 'conversations',
  chat: true,
  fields: [{ name: 'channel', type: 'text' }],
}
```

FrogBot preserves compatible authored fields, appends missing role fields, and combines hooks. Incompatible definitions and reserved role fields fail configuration instead of being silently replaced. See [Collection Configs](https://docs.frogbot.ai/configuration/collections.md#role-markers) for each role's contribution.

## Admin options

Use a field's `admin` property to control its presentation without changing API behavior.

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

export const Summary: Field = {
  name: 'summary',
  type: 'textarea',
  admin: {
    description: 'Shown below the title on listing pages.',
    position: 'main',
    width: '100%',
  },
}
```

Every data field accepts these options. Each field type's page lists the extra options it adds and any of these it doesn't support.

| Option | Type | Default | Description |
| - | - | - | - |
| `condition` | `(data, siblingData, ctx) => boolean` | — | Shows the field only when it returns `true`. The server also uses it to skip validating hidden fields. See [Conditional logic](#conditional-logic). |
| `components` | `object` | — | Replaces the field or one of its component slots. See [Custom components](#custom-components). |
| `description` | `string \| Record<string, string> \| (({ i18n, t }) => string)` | — | Editor guidance shown with the field and written as a comment in generated types. See [Field descriptions](#field-descriptions). |
| `position` | `'main' \| 'sidebar'` | `'main'` | Places the field in the main form or the edit view's sidebar. |
| `width` | `string \| number` | Full width | CSS width, especially within a row. |
| `style` | `CSSProperties` | — | Inline CSS properties added to the field root. |
| `className` | `string` | — | CSS class added to the field root. |
| `readOnly` | `boolean` | `false`, or `true` for virtual fields | Prevents editing in the admin panel but does not restrict API writes. |
| `disabled` | `boolean \| { field?, column?, filter?, groupBy?, bulkEdit? }` | `false` | `true` omits the field everywhere in the admin panel. An object omits it from one area each: the edit form, list columns, list filters, list grouping or bulk editing. |
| `hidden` | `boolean` | `false` | Hides the input while retaining its submitted value. |
| `custom` | `Record<string, any>` | — | Metadata available on the server and in the browser. Use the field's top-level `custom` for server-only data. |

### Field descriptions

Descriptions give editors instructions or context near a field. Their exact placement depends on the field type. A description can be a string, a server-side function that returns a string, or a custom React component.

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

export const Articles: CollectionConfig = {
  slug: 'articles',
  fields: [
    {
      name: 'summary',
      type: 'textarea',
      admin: {
        description: 'Keep the summary under 160 characters.',
      },
    },
  ],
}
```

> [!WARNING]
>
> To replace the description with a custom component, set `admin.components.Description` instead. See [Description components](#description).

#### Description functions

Description functions run on the server and are useful for simple localized text. They receive `t`, the translation function used by the admin panel, and the `i18n` object.

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

export const Articles: CollectionConfig = {
  slug: 'articles',
  fields: [
    {
      name: 'title',
      type: 'text',
      admin: {
        description: ({ t }) => t('general:title'),
      },
    },
  ],
}
```

| Argument | Type | Description |
| - | - | - |
| `t` | `TFunction` | Translates a configured key for the current admin locale. |
| `i18n` | `I18nClient` | The admin internationalization object, including the current `language`. |

> [!NOTE]
>
> A description function is not reactive to live form changes. Use a client Description component when the content must subscribe to form state.

### Conditional logic

An `admin.condition` function receives the full form data, sibling data, and context about the field's location and current user. Return `true` to display the field.

| Argument | Type | Description |
| - | - | - |
| `data` | `Partial<TData>` | All data in the Document currently being edited. |
| `siblingData` | `Partial<TSiblingData>` | Data belonging to fields under the same parent. |
| `ctx` | `object` | The field location, operation, nearest block, and authenticated user, described below. |

The context object contains:

| Property | Type | Description |
| - | - | - |
| `blockData` | `Partial<TData>` | Data for the nearest parent block, or `undefined` outside a block. |
| `operation` | `'create' \| 'read' \| 'update' \| 'delete'` | The operation the field is currently participating in. |
| `path` | `(string \| number)[]` | The full runtime field path, including array indexes. |
| `user` | `User \| null` | The authenticated user, or `null`. |

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

export const Profiles: CollectionConfig = {
  slug: 'profiles',
  fields: [
    {
      name: 'showGreeting',
      type: 'checkbox',
      defaultValue: false,
    },
    {
      name: 'greeting',
      type: 'text',
      admin: {
        condition: (data, siblingData, { blockData, path, user }) => {
          return data.showGreeting === true
        },
      },
    },
  ],
}
```

Conditions affect only the editing interface. Use field access control to protect data returned from or written through APIs.

## Custom components

Fields are represented in several places in the admin panel: the form field in an Edit view, a table cell and filter in a List view, and a diff when comparing versions. Set `admin.components` to replace any of these surfaces or one of the smaller field slots. Component paths are resolved through FrogBot's import map.

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

export const Color: Field = {
  name: 'color',
  type: 'text',
  admin: {
    components: {
      Field: '/components/ColorField#ColorField',
      Cell: '/components/ColorCell#ColorCell',
    },
  },
}
```

The following component options are available:

| Component | Type | Default | Description |
| - | - | - | - |
| `Field` | `FrogBotComponent` | Built-in | The form control rendered in the Edit view. [Details](#field). |
| `Cell` | `FrogBotComponent` | Built-in | The value rendered in a List view table. [Details](#cell). |
| `Filter` | `FrogBotComponent` | Built-in | The control rendered in the List view's filter menu. [Details](#filter). |
| `Label` | `FrogBotComponent` | Built-in | Replaces the field label. [Details](#label). |
| `Description` | `FrogBotComponent` | Built-in | Replaces the field description. [Details](#description). |
| `Error` | `FrogBotComponent` | Built-in | Replaces the field validation error. [Details](#error). |
| `Diff` | `FrogBotComponent` | Built-in | Replaces the field's Version Diff rendering. [Details](#diff). |
| `beforeInput` | `FrogBotComponent[]` | — | Adds components immediately before the input. [Details](#afterinput-and-beforeinput). |
| `afterInput` | `FrogBotComponent[]` | — | Adds components immediately after the input. [Details](#afterinput-and-beforeinput). |

Layout fields support a subset: Row fields, Tabs fields, and individual tabs have only `Field`, `Cell`, `Filter`, `Description`, and `Diff`; Group and Collapsible fields have no `Error` slot; and UI fields are described on the [UI field](https://docs.frogbot.ai/fields/ui.md#admin-options) page. Array fields add `RowLabel`.

### Field

The Field component is the form control editors interact with in an Edit view. Replace it with `admin.components.Field`:

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

export const Color: Field = {
  name: 'color',
  type: 'text',
  admin: {
    components: {
      Field: '/components/ColorField#ColorField',
    },
  },
}
```

> [!WARNING]
>
> You do not have to replace the entire Field component. Use `Label`, `Description`, `Error`, `beforeInput`, or `afterInput` to customize one part while preserving the default form control.

#### Default props

Exact props vary by field family, so type your component with that family's exported prop or component type. Client field components receive:

| Property | Type | Description |
| - | - | - |
| `field` | Client field config | The serializable field configuration. Functions such as hooks and access are removed. |
| `path` | `string` | The dynamic runtime path, such as `settings.items.0.title`. |
| `schemaPath` | `string \| undefined` | The static configuration path, without array indexes. Defaults to the field name. |
| `readOnly` | `boolean \| undefined` | Whether the form control is read-only. |
| `permissions` | `SanitizedFieldPermissions \| undefined` | The current user's permissions for this field. |
| `validate` | Field validator | The field's validation function, for families that validate a value. |
| `forceRender` | `boolean \| undefined` | Renders the field immediately instead of waiting until it scrolls into view. |
| `renderedBlocks` | `RenderedField[] \| undefined` | Server-rendered block rows, used by Blocks fields. |

Some families add props, such as `inputRef` and `onKeyDown` on Text fields.

Server field components receive the same `path`, `schemaPath`, `readOnly`, `permissions`, `forceRender`, and `renderedBlocks` props, plus:

| Property | Type | Description |
| - | - | - |
| `field` | `Field` | The authored field configuration, including functions. |
| `clientField` | Client field config | The serializable client field configuration, ready to pass to a client component. |
| `collectionSlug` | `string` | The Collection being edited. |
| `data` | `Data` | The current Document data. |
| `siblingData` | `Data` | Data belonging to sibling fields. |
| `value` | `unknown` | The field value at render time. |
| `id` | `string \| number \| undefined` | The current Document ID, or `undefined` during create. |
| `operation` | `'create' \| 'read' \| 'update' \| 'delete'` | The current operation. |
| `formState` | `FormState` | The complete form state. |
| `preferences` | `DocumentPreferences` | The Document's saved admin preferences. |
| `i18n` | `I18nClient` | The admin internationalization object. |
| `req` | `FrogBotRequest` | The framework-issued request, including `req.frogbot` and `req.user`. |
| `user` | `User` | The authenticated user with field read access applied. Use `req.user` for access checks. |
| `customComponents` | `object \| undefined` | The rendered `AfterInput`, `BeforeInput`, `Description`, `Error`, `Field`, and `Label` slots. |
| `fieldSchemaMap` | `FieldSchemaMap` | Map of every field configuration in the form, keyed by schema path. |
| `clientFieldSchemaMap` | `ClientFieldSchemaMap` | The client-safe version of `fieldSchemaMap`. |

FrogBot's server prop types expose `req.frogbot` and do not expose a separate top-level framework instance.

#### Sending and receiving form values

Client components manage form state with hooks and field primitives from `@frogbotai/ui/admin`. Import the corresponding component type from `frogbot`.

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

import type { TextFieldClientProps } from 'frogbot'
import { FieldLabel, useField } from '@frogbotai/ui/admin'

export const ColorField = ({ field, path }: TextFieldClientProps) => {
  const { setValue, value } = useField<string>({ path })

  return (
    <label>
      <FieldLabel label={field.label} path={path} required={field.required} />
      <input type="color" value={value ?? '#000000'} onChange={(event) => setValue(event.target.value)} />
    </label>
  )
}
```

Client field components receive serializable field and form props. They do not receive a server request or FrogBot instance.

#### Server field components

Server component types follow the same field-specific naming convention. For example, `TextFieldServerProps` and `TextFieldServerProps` are exported from `frogbot`. Their framework-issued `req` is a `FrogBotRequest`, and `req.frogbot` is attached before the component renders, including form-state rebuilds.

```tsx theme={"theme":"tokyo-night"}
import type { TextFieldServerProps } from 'frogbot'

export const CollectionSummary = ({ req }: TextFieldServerProps) => {
  const count = Object.keys(req.frogbot.collections).length

  return <p>{count} collections are available.</p>
}
```

Do not construct this request yourself or use a fallback instance lookup. Components that genuinely have no request prop can import the app config and call `getFrogBot({ config })` when they need server access.

#### Field component types

Each field family exports the props its components receive from `frogbot`: `*FieldClientProps` and `*FieldServerProps`, plus `*FieldLabel*Props`, `*FieldDescription*Props`, `*FieldError*Props` and `*FieldDiff*Props`. Type a component by its props, as in `({ field }: TextFieldServerProps) => …`. UI fields do not have label, description, or error props; Blocks fields additionally provide `BlockRowLabelServerProps` and `BlockRowLabelClientProps`.

```tsx theme={"theme":"tokyo-night"}
import type {
  TextFieldClientProps,
  TextFieldClientProps,
  TextFieldServerProps,
  TextFieldServerProps,
} from 'frogbot'
```

See the individual field-type pages for the exact types supported by each family.

### Cell

The Cell component renders a field's value in a List view table. Replace it with `admin.components.Cell`:

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

export const Price: Field = {
  name: 'price',
  type: 'number',
  admin: {
    components: {
      Cell: '/components/PriceCell#PriceCell',
    },
  },
}
```

A custom `Cell` appears only in the List view; [Board](https://docs.frogbot.ai/admin/collection-board.md) cards and [Calendar](https://docs.frogbot.ai/admin/collection-calendar.md) events show FrogBot's built-in display for the field. To draw a field the same way in a custom Board `Card` or Calendar `Event`, render `FieldCell` from `@frogbotai/ui/admin` with the Cell props below.

Cells use the generic `DefaultCellComponentProps` or `DefaultServerCellComponentProps` types from `frogbot`. Cell components do not receive field-component form props or a request.

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

import type { DefaultCellComponentProps } from 'frogbot'

export function PriceCell({ cellData, rowData }: DefaultCellComponentProps) {
  const price = Number(cellData)
  const currency = typeof rowData.currency === 'string' ? rowData.currency : 'USD'

  return new Intl.NumberFormat('en-US', { currency, style: 'currency' }).format(price)
}
```

> [!WARNING]
>
> Use `DefaultCellComponentProps` for a client component with `'use client'`, and `DefaultServerCellComponentProps` for a server component. They preserve different default generic types.

Cell props include:

| Property | Type | Description |
| - | - | - |
| `cellData` | `any` | The value for this field and row. |
| `collectionSlug` | `string` | The collection being listed. |
| `field` | Client field config | The serializable field configuration. Server cells receive the authored `Field` instead. |
| `rowData` | `RowData` | All data for the current row. |
| `className` | `string \| undefined` | An optional CSS class. |
| `columnIndex` | `number \| undefined` | The column's position in the table. |
| `customCellProps` | `Record<string, any> \| undefined` | Additional props passed by the table. |
| `link` | `boolean \| undefined` | Whether the cell should be rendered as a link. |
| `linkURL` | `string \| undefined` | The link destination. |
| `onClick` | `({ cellData, collectionSlug, rowData }) => void` | An optional click handler. |
| `viewType` | `ViewTypes \| undefined` | The current view type. |

Server cells additionally receive `collectionConfig`, the sanitized collection configuration, and `i18n`, the admin internationalization object.

> [!NOTE]
>
> Cell components are optimized for table rendering. They do not receive Field component props such as `preferences`, `validate`, or `path`, and they do not receive a request.

### Filter

The Filter component is the input shown in the List view's **Filter By** menu. It represents this field when an editor builds a query.

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

export const Status: Field = {
  name: 'status',
  type: 'text',
  admin: {
    components: {
      Filter: '/components/StatusFilter#StatusFilter',
    },
  },
}
```

A Filter component must be a client component. FrogBot renders it without filter-specific props: it receives only the `clientProps` set in its component configuration. Custom filters apply to top-level fields only.

### Label

The Label component is used wherever the admin panel labels a field, most commonly in Edit views.

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

export const Title: Field = {
  name: 'title',
  type: 'text',
  admin: {
    components: {
      Label: '/components/TitleLabel#TitleLabel',
    },
  },
}
```

Label component types follow the field-family convention:

```tsx theme={"theme":"tokyo-night"}
import type {
  TextFieldLabelClientProps,
  TextFieldLabelServerProps,
} from 'frogbot'
```

### Description

Use a Description component instead of the [`admin.description`](#field-descriptions) property when the description needs rich markup, live form values, or interaction.

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

export const Title: Field = {
  name: 'title',
  type: 'text',
  admin: {
    components: {
      Description: '/components/TitleDescription#TitleDescription',
    },
  },
}
```

Description component types follow the same convention:

```tsx theme={"theme":"tokyo-night"}
import type {
  TextFieldDescriptionClientProps,
  TextFieldDescriptionServerProps,
} from 'frogbot'
```

### Error

The Error component renders when field validation fails, usually beneath the input.

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

export const Title: Field = {
  name: 'title',
  type: 'text',
  admin: {
    components: {
      Error: '/components/TitleError#TitleError',
    },
  },
}
```

Error component types follow the same convention:

```tsx theme={"theme":"tokyo-night"}
import type {
  TextFieldErrorClientProps,
  TextFieldErrorServerProps,
} from 'frogbot'
```

### Diff

The Diff component renders a field in the Version Diff view and is visible only when versioning is enabled.

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

export const Title: Field = {
  name: 'title',
  type: 'text',
  admin: {
    components: {
      Diff: '/components/TitleDiff#TitleDiff',
    },
  },
}
```

Diff component types follow the same convention:

```tsx theme={"theme":"tokyo-night"}
import type {
  TextFieldDiffClientProps,
  TextFieldDiffServerProps,
} from 'frogbot'
```

### afterInput and beforeInput

Use `beforeInput` and `afterInput` to add components around the input without replacing the complete Field component. Each option accepts an array, so multiple components can occupy either slot.

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

export const Title: Field = {
  name: 'title',
  type: 'text',
  admin: {
    components: {
      beforeInput: ['/components/TitlePrefix#TitlePrefix'],
      afterInput: ['/components/TitleCounter#TitleCounter'],
    },
  },
}
```

These slot components receive the field family's default component props. Choose the corresponding client or server type based on whether the component uses `'use client'`.

Use only the curated admin hooks and field primitives exported by `@frogbotai/ui/admin`. General-purpose admin internals and element subpaths are not part of this contract. Component paths, server and client boundaries, and import-map generation follow the conventions above.

## TypeScript

Import `Field`, individual field configuration types, and custom component types from `frogbot`. Container field types are recursive, so nested fields retain FrogBot hook, access, and validation request types.

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

Run `frogbot generate:types` to generate your application's `frogbot-types.ts` declarations from the configured collections and fields. See [TypeScript](https://docs.frogbot.ai/typescript/overview.md) for generated type setup.


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