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

# AI Field

> Fill a field automatically with AI from other fields of the same record, and keep it up to date as they change.

`aiField` adds a field that FrogBot fills in with AI. You name the fields it reads and write a prompt. When one of those fields changes, FrogBot asks the model in the background and saves the answer. The admin panel shows when a value is being generated, when it failed, and when someone wrote it by hand.

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

export const Tasks: CollectionConfig = {
  slug: 'tasks',
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'notes', type: 'textarea' },
    aiField({
      name: 'summary',
      inputs: ['title', 'notes'],
      prompt: 'Summarize this task in one sentence.',
    }),
  ],
}
```

The value is a [Text field](https://docs.frogbot.ai/fields/text.md), or a [Select field](https://docs.frogbot.ai/fields/select.md) when you pass `options` (see [Select output](#select-output)). `aiField` returns a row holding the value and two state fields, `_summary_status` and `_summary_error` (see [Stored value](#stored-value)). Put it in a collection's `fields` like any other field.

## Config options

| Option | Type | Default | Description |
| - | - | - | - |
| `inputs` | `string[]` | Required | Names of the fields the model reads. Each must be a top-level field of the same collection. A change to any of them regenerates the value. See [Placement and inputs](#placement-and-inputs). |
| `prompt` | `string` | Required | Instructions for the model, such as `'Summarize this task in one sentence.'`. See [What the model sees](#what-the-model-sees). |
| `model` | `ModelId` | The [utility model](https://docs.frogbot.ai/ai/overview.md#choose-a-utility-model) | Model that generates the value: a configured chat model ID, such as `'openai/gpt-5-nano'`, or a [router](https://docs.frogbot.ai/ai/overview.md#add-aliases) name. Without it, FrogBot uses `ai.smallModel`, or picks a low-cost model from `ai.defaultModel`'s provider. |
| `name` | `string` | Required | Property name used in stored records and APIs. Also names the state fields. See [field names](https://docs.frogbot.ai/fields/overview.md#field-names). |
| `minLength` | `number` | — | Text output only. Minimum length, checked by the built-in validator on every save, including a run's. |
| `maxLength` | `number` | Root `defaultMaxTextLength` (`40000`) | Text output only. Maximum length, checked by the built-in validator on every save. A longer answer fails the run. |
| `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. |
| `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, and localizes the state fields with it. See [Locales and drafts](#locales-and-drafts). |
| `defaultValue` | `string \| string[]` or `({ locale, req, user }) => value` | — | Value used when the field has no value. It counts as a hand-written value, so a record created with it starts as `manual` and isn't generated until someone regenerates it. |
| `validate` | `Validate` | Built-in validator | Replaces the built-in validator. It also runs on a run's save, so a failing answer is saved as an error. 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. `read` decides who sees the generated value. See [field access control](https://docs.frogbot.ai/access-control/fields.md). |
| `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). |
| `disableDuplicate` | `boolean` | `false` | Leaves the value out when a document is duplicated. Without it, a duplicate keeps the value as a hand-written one. |
| `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) and agents. They run after the AI field's own entry. See [Agents and generated types](#agents-and-generated-types). |

With `options`, these Select options are also accepted, and `minLength` and `maxLength` are not:

| Option | Type | Default | Description |
| - | - | - | - |
| `options` | `(string \| { label: string \| Record<string, string>; value: string; color?: OptionColor })[]` | — | Values the model may pick from. Must be non-empty, and every value a non-empty string. `color` shows the option as a coloured pill. See [Select output](#select-output). |
| `hasMany` | `boolean` | `false` | Lets the model pick any number of options, stored as an ordered array. |
| `filterOptions` | `({ data, options, req, siblingData }) => Option[]` | — | Returns the options shown and accepted for the current record. The model still sees every option, so an answer outside the filtered list fails validation. See [filter options](https://docs.frogbot.ai/fields/select.md#filter-options). |
| `enumName` | `string \| ({ tableName }) => string` | Generated from the field name | SQL enum name. |
| `dbName` | `string \| ({ tableName }) => string` | Generated from the field name | SQL table name for the values of a `hasMany` field. |
| `interfaceName` | `string` | — | Generates a reusable top-level TypeScript type for the options. |

`required` isn't supported: a generated value is empty until its first run finishes, and an empty answer is allowed. FrogBot stores the value and shows it to everyone who can read the record, so `virtual` and `hidden` aren't supported either. A text AI field also holds one value, so `hasMany`, `minRows` and `maxRows` are not accepted without `options`. TypeScript rejects all of them, and from JavaScript they fail when the config is built, as do invalid options:

```text theme={"theme":"tokyo-night"}
aiField "summary": inputs must be a non-empty array of field names
aiField "summary": prompt must be a non-empty string
aiField "summary": model must be a non-empty string
aiField "summary": required is not supported
aiField "summary": hasMany is not supported
aiField "summary": virtual is not supported
aiField "type": options must be a non-empty array
aiField "type": option values must be non-empty strings
```

## Admin options

AI fields support the standard [field admin options](https://docs.frogbot.ai/fields/overview.md#admin-options). `admin.position` applies to the whole row, so the value and its status move to the sidebar together. Every other option applies to the value field. `admin.readOnly` stops hand edits but keeps Generate and Regenerate for users who may update the record.

Text output also accepts the [Text admin options](https://docs.frogbot.ai/fields/text.md#admin-options):

| Option | Type | Default | Description |
| - | - | - | - |
| `placeholder` | `string \| Record<string, string>` | — | Placeholder text. Use an object keyed by language code for translations. |
| `autoComplete` | `string` | — | Value of the input's `autocomplete` attribute. |
| `rtl` | `boolean` | Follows the locale | `true` forces right-to-left text. `false` keeps left-to-right text in a right-to-left locale. When unset, right-to-left locales render right-to-left. |

Select output accepts the [Select admin options](https://docs.frogbot.ai/fields/select.md#admin-options):

| Option | Type | Default | Description |
| - | - | - | - |
| `isClearable` | `boolean` | `true` | Shows a control that clears the selection. |
| `isSortable` | `boolean` | `true` | Lets editors reorder selections when `hasMany` is `true`. |
| `placeholder` | `string \| (({ i18n, t }) => string)` | — | Placeholder text shown when nothing is selected. |

FrogBot stores the field's inputs, prompt and model in `admin.custom.frogbot.kind`. Your own `admin.custom` keys, and other keys under `admin.custom.frogbot`, are kept.

> [!WARNING]
>
> `admin.custom` is sent to the browser, so anyone who can open the admin panel can read the prompt. Don't put secrets in it.

## Display and editing

### List

The [List](https://docs.frogbot.ai/admin/overview.md) cell shows the value with its state:

| State | Cell |
| - | - |
| Never run, inputs set | The value, if any, and a **Generate** button. |
| Never run, inputs empty | Nothing. |
| Generating | The previous value, dimmed, with a spinner. |
| Done | The value, with a **Regenerate** button on hover. |
| Failed | The previous value, if any, and a warning button. Hover it to read the error, and click it to retry. |
| Edited by hand | The value, with a **Regenerate** button on hover. |

The buttons need update access to the record. Without it, the cell shows the value, the spinner and the warning only. Generating cells update on their own: while any are visible, the page re-reads them in one request every 3 seconds, and stops once none are generating.

If the collection sets `admin.enableListViewSelectAPI`, the List shows the plain value, with no state and no buttons.

### Board and Calendar

[Board](https://docs.frogbot.ai/admin/collection-board.md) cards and [Calendar](https://docs.frogbot.ai/admin/collection-calendar.md) events show the value, dimmed while generating, and a warning icon after a failure. They have no buttons. Board can group by an AI select field.

### Document view

The document view shows a line under the input:

| State | Line |
| - | - |
| Never run | Generate |
| Generating | Generating… |
| Done | Regenerate |
| Failed | Failed: \<error> · Retry |
| Edited by hand | Edited by hand, won't update automatically · Regenerate |

Generate, Regenerate and Retry need update access to the record, and the line is hidden while creating a record. While the value is generating, the view checks for the result every 3 seconds. A result that arrives while the form is open fills the input, unless you have already changed it.

A Regenerate is refused while another user is editing the record, and the error appears as a message. On a collection with drafts, it is also refused when the record was saved as a draft or published, or started generating, after the view loaded, so it never publishes a newer draft:

```text theme={"theme":"tokyo-night"}
This record changed since it was loaded. Reload it and try again.
```

Typing in the input and saving keeps your value. See [The manual-edit rule](#the-manual-edit-rule).

## Runs

### When runs happen

A save queues a run when it:

* creates a record with at least one input set;
* changes an input of a record that wasn't edited by hand;
* sets the status to `pending` ([Regenerate](#regenerate)).

The run happens in the background, after the save has finished. A save that fails, or a transaction that rolls back, queues nothing.

An AI field can be an input of another AI field: when the first one is saved, the second one runs. FrogBot refuses to start if AI fields form a cycle.

### What the model sees

The prompt is the model's instructions, and the message is a JSON object of the input values keyed by field name, in `inputs` order:

```json theme={"theme":"tokyo-night"}
{"title":"Ship the order","notes":"Courier picks up Friday"}
```

Values are read as stored: relationships are IDs and rich text is its stored JSON. The model has no tools, and the answer is saved with surrounding whitespace removed.

### The manual-edit rule

Writing a value by hand, from the admin panel or any API, marks the field as edited by hand. FrogBot then keeps that value: input changes no longer regenerate it, until someone regenerates it.

A hand edit made while a run is in progress wins, and the run's answer is discarded. Clearing a generated value by hand also counts as an edit.

### Regenerate

Generate, Regenerate and Retry all set the status to `pending`, which queues a run and clears the edited-by-hand state. You can do the same from the REST API, the Local API or an agent tool:

```ts theme={"theme":"tokyo-night"}
await req.frogbot.update({
  collection: 'tasks',
  overrideAccess: true,
  id,
  data: { _summary_status: 'pending' },
  req,
})
```

This needs update access to the record. Any other status you write is ignored. To regenerate many records at once, see [Regenerate many records](#regenerate-many-records).

### Quick edits

Quick edits leave at most one waiting run per record, field and locale, and it uses the latest inputs. A run that has already started finishes first.

An answer generated from inputs that have changed since is never saved. See [Jobs](https://docs.frogbot.ai/jobs-queue/overview.md).

### Who a run belongs to

A run belongs to the user who made the save, and its usage is recorded for that user. It follows the [AI access rules](https://docs.frogbot.ai/ai/overview.md#control-access) for that user, including their budget and allowed models with the [AI policy plugin](https://docs.frogbot.ai/plugins/ai-policy.md). A run that a policy refuses is saved as a failure.

A user's run reads only the inputs that user can read, so two users can get different answers for the same record. A save with no user, such as a Local API call or a seed, runs with no per-user limits and reads every input.

The answer is visible to anyone who can read the AI field, so it can reveal inputs they can't read. Use the field's `access.read` when that matters.

If the user is deleted before the run, or can no longer read the record, the run fails with one of these errors:

```text theme={"theme":"tokyo-night"}
The user who started this run no longer exists.
The user who started this run can't read this record.
```

A run's save sets `updatedBy` to the user who started the run.

### Failures

A failed run, such as a provider error, a refused policy or an answer that fails validation, sets the status to `error` and saves the first 200 characters of the message. The full error is written to the server log. Failed runs are not retried automatically; use Retry or Regenerate.

### Locales and drafts

A localized AI field runs per locale: a save in `fr` regenerates only the `fr` value, from the `fr` inputs.

On a collection with drafts, a run reads and writes the latest version. A draft's run saves to the draft and leaves the published version unchanged. If someone saves a newer draft of a published record while the run is in progress, the answer goes to that draft, and the draft stays unpublished.

### The job runner

Runs are jobs on the `default` queue. The web server checks for waiting runs once a minute; a [worker](https://docs.frogbot.ai/jobs-queue/workers.md) can check more often. If web autorun is turned off and no worker runs, values stay generating.

## Regenerate many records

The List's **…** menu has a **Regenerate \<label>…** entry for each AI field, such as **Regenerate Summary…**. It's shown to users who can update the collection, and not in the trash or in a relationship drawer.

The entry covers the selected rows. With nothing selected, or after **Select all**, it covers every record that matches the List's filters and search.

> [!NOTE]
>
> A [view's `filter`](https://docs.frogbot.ai/configuration/collections#shared-view-properties) isn't applied, so **All in view** and **Select all** can include records that the view hides. Add the condition as a List filter to leave them out.

The entry opens a dialog that asks which records to run:

| Choice | Runs records that are |
| - | - |
| **All selected**, or **All in view** with nothing selected | Never run, done, failed or edited by hand. It's the only choice that replaces values edited by hand. |
| **Only values written by AI** | Done. |
| **Only failed** | Failed. |
| **Only never generated** | Never run. This is the default. |

No choice runs records that are generating or whose inputs are all empty. The dialog doesn't count records in advance; choose **Regenerate** to queue the runs, or **Cancel** to change nothing.

When the runs are queued, the dialog closes and a message reports the result:

```text theme={"theme":"tokyo-night"}
Queued 12 runs · 3 records skipped (no inputs, no permission, being edited, or changed)
```

A record is skipped when its inputs are all empty, you can't update it, another user is editing it, or its status changed after you chose. The selection is cleared, and the queued rows show a spinner and fill in as their runs finish. If queueing stops partway, for example after a network error, the message gives the error and how many runs were already queued; those runs still happen.

Each record is regenerated as a single [Regenerate](#regenerate) would do it:

* Drafts stay drafts, and published records stay published.
* The runs use the List's locale.
* The runs belong to you and follow your [AI access rules](https://docs.frogbot.ai/ai/overview.md#control-access), such as your budget and allowed models. See [Who a run belongs to](#who-a-run-belongs-to). A record your policy refuses, for example after your budget runs out partway, shows the failed state. Choose **Only failed** later to retry it.

Runs go at the job runner's pace. With the defaults, the web server checks once a minute and runs up to 10 waiting runs, so 500 records take roughly an hour. To go faster, run a [worker](https://docs.frogbot.ai/jobs-queue/workers.md) with a shorter `--cron` and a higher `--limit`.

From the [REST API](https://docs.frogbot.ai/rest-api/overview.md), the same result is a normal update that writes `pending` to the status field of every record matching a filter. This retries every failed summary:

```text theme={"theme":"tokyo-night"}
PATCH /api/tasks?where[_summary_status][equals]=error
{ "_summary_status": "pending" }
```

On a collection with drafts, add `draft=true` and update drafts and published records separately, with a `_status` condition in the filter. For published records, also send `"_status": "published"` so they stay published. A plain update without `draft=true` publishes over any newer draft.

## Select output

Pass `options` to have the model pick from a list:

```ts theme={"theme":"tokyo-night"}
aiField({
  name: 'type',
  inputs: ['title', 'notes'],
  prompt: 'Classify this request.',
  options: [
    { label: 'Bug', value: 'bug', color: 'red' },
    { label: 'Feature', value: 'feature', color: 'blue' },
    'question',
  ],
})
```

The model is asked for a structured answer and sees the option values, not the labels, so use values that explain themselves. With `hasMany: true` it can pick any number, and duplicates are dropped in the model's order.

An empty answer is allowed and saved as no value. An answer that isn't an option, or that isn't a structured answer at all, fails the run with Retry. The model needs to support structured output.

Changing the options regenerates nothing. A record whose stored value was removed from the options behaves as it would on any Select field: every save, including Regenerate, fails with "This field has an invalid selection" until the value is changed by hand.

AI select values appear as option pills in List cells and on cards, coloured when the options have colours.

## Placement and inputs

An AI field must be a top-level field of a collection. Rows, collapsibles, unnamed groups and unnamed tabs are fine; arrays, blocks, named groups and named tabs are not. Inputs follow the same rule.

FrogBot checks AI fields at startup and refuses to start with an error such as:

```text theme={"theme":"tokyo-night"}
[frogbot] aiField "summary" in collection "tasks": input "body" is not a top-level field of the collection
[frogbot] aiField "summary" in collection "tasks": it lists itself as an input
[frogbot] aiField "summary" in collection "tasks": AI fields form a cycle: summary → category → summary
[frogbot] aiField "summary" in collection "tasks": "_summary_status" is already used by another field
[frogbot] aiField "summary" in collection "tasks": model 'openai/gpt-5-nano' is not configured
[frogbot] aiField "summary" in collection "tasks": model 'openai/text-embedding-3-small' is not a chat model
[frogbot] aiField "summary" in collection "tasks": no model. Set `model` on the field, or `ai.smallModel` or `ai.defaultModel`
[frogbot] aiField "summary" must be a top-level field of a collection; found in an array, blocks, group or named tab in collection "tasks"
[frogbot] Job task slug 'frogbot-run-ai-field' is reserved for AI fields.
```

## Stored value

The value is stored like a Text or Select field. Next to it, FrogBot keeps two state fields:

| Field | Values |
| - | - |
| `_<name>_status` | `pending` (generating), `done`, `error` or `manual` (edited by hand). Empty means it has never run. |
| `_<name>_error` | The message from the last failed run, or empty. |

Both are returned by the REST API, the Local API and agent tools, and they're kept out of admin forms, List columns and filters. FrogBot sets them; the only write it accepts is the status `pending`. They are localized with the value.

## Agents and generated types

An AI field describes itself to agents using FrogBot's [MCP tools](https://docs.frogbot.ai/plugins/mcp.md) and in [generated types](https://docs.frogbot.ai/typescript/generating-types.md). The value's text names the inputs:

```text theme={"theme":"tokyo-night"}
Generated automatically by AI from: title, notes. Writing a value keeps it and stops automatic updates
```

The state fields have their own:

```text theme={"theme":"tokyo-night"}
Set by FrogBot: pending, done, error or manual. Write "pending" to regenerate the value
Set by FrogBot: the error message from the last failed run
```

With a string `admin.description`, or a translated one with an `en` entry, the AI text is added after it in parentheses. The admin panel still shows only your description. A function `admin.description` isn't copied, so the AI text stands alone. The text is English only.

To change the value's text, add your own `jsonSchema` entry. It runs after the AI field's entry, so it can adjust or replace the description.

## Custom components

The AI cell and input are `AICell` and `AIField` from `@frogbotai/ui/admin`. Your own `admin.components.Cell` or `admin.components.Field` replaces them, and other components such as `Label`, `Description`, `Error`, `beforeInput` and `afterInput` work as they do on a Text or Select field.

A custom Cell can draw the standard AI display inside its own markup with [`FieldCell`](https://docs.frogbot.ai/fields/overview#cell). As with any custom Cell, it appears only in the List view.

`frogbot/fields` exports `aiFieldPaths(name)`, which returns the state field names, `isAIFieldInputSet(value)`, and the `AIFieldStatus` and `AIKind` types. It is safe to use in browser code.


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