---
title: "Fields reference"
description: "Generated field APIs, projections, validation, and table counterparts."
image: "https://adonia.pages.dev/og.png"
version: "next"
---

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

# Fields reference

<!-- Generated by `pnpm docs:fields`. Edit the field classes' TSDoc, not this file. -->

One page per field type, generated from the field classes themselves: their TSDoc, their
builder methods, the detail projection and table cell they declare, and the Vine
base they contribute. Descriptor protocol v1.

Fields are the state-bearing nodes of the schema tree. Each page covers what its *type*
adds; the DSL every field shares — chrome, projection, state, validation, reactivity —
is [below](#the-shared-field-dsl).

## Field types

| Type key | Builder | Detail projection | Column |
| --- | --- | --- | --- |
| [`text-input`](/reference/fields/text-input) | `F.text` | `text-entry` | `text` |
| [`textarea`](/reference/fields/textarea) | `F.textarea` | `text-entry` | `text` |
| [`number`](/reference/fields/number) | `F.number` | `text-entry` | `number` |
| [`slider`](/reference/fields/slider) | `F.slider` | `text-entry` | `number` |
| [`select`](/reference/fields/select) | `F.select` | `text-entry` | `text` |
| [`radio`](/reference/fields/radio) | `F.radio` | `text-entry` | `text` |
| [`checkbox`](/reference/fields/checkbox) | `F.checkbox` | `text-entry` | `boolean` |
| [`toggle`](/reference/fields/toggle) | `F.toggle` | `text-entry` | `boolean` |
| [`checkbox-list`](/reference/fields/checkbox-list) | `F.checkboxList` | `text-entry` | `text` |
| [`date`](/reference/fields/date) | `F.date` | `text-entry` | `date` |
| [`datetime`](/reference/fields/datetime) | `F.datetime` | `text-entry` | `date` |
| [`time`](/reference/fields/time) | `F.time` | `text-entry` | `date` |
| [`color`](/reference/fields/color) | `F.color` | `text-entry` | `text` |
| [`hidden`](/reference/fields/hidden) | `F.hidden` | `text-entry` | — |
| [`repeater`](/reference/fields/repeater) | `F.repeater` | `repeater-entry` | — |
| [`key-value`](/reference/fields/key-value) | `F.keyValue` | `key-value-entry` | — |
| [`json`](/reference/fields/json) | `F.json` | `code-entry` | — |
| [`code`](/reference/fields/code) | `F.code` | `code-entry` | — |
| [`markdown`](/reference/fields/markdown) | `F.markdown` | `text-entry` | `text` |
| [`rich-text`](/reference/fields/rich-text) | `F.richText` | `html` | — |
| [`file`](/reference/fields/file) | `F.file` | `text-entry` | `text` |
| [`image`](/reference/fields/image) | `F.image` | `image-entry` | `image` |
| [`belongs-to`](/reference/fields/belongs-to) | `F.belongsTo` | `relation-entry` | — |
| [`belongs-to-many`](/reference/fields/belongs-to-many) | `F.belongsToMany` | `relation-entry` | — |
| [`has-many`](/reference/fields/has-many) | `F.hasMany` | `repeater-entry` | — |
| [`morph-to`](/reference/fields/morph-to) | `F.morphTo` | `relation-entry` | — |

The projection column shows a DEFAULT instance. Choice fields decide per instance —
a `select` whose options carry colours projects to `badge-entry` and derives a `badge`
cell; its own page says so.

## The shared field DSL

Every field inherits these from `Field` and `SchemaComponent`
(`packages/core/src/schema/`). Listed once here instead of on all 26 type pages.

| Method | Declared on | Behaviour |
| --- | --- | --- |
| `label(text: string): this` | `Field` | Human label rendered above the input / beside the entry. |
| `helper(text: string): this` | `Field` | Helper text rendered under the input. |
| `placeholder(text: string): this` | `Field` | Input placeholder. |
| `prefix(text: string): this` | `Field` | Static text rendered inside the control, before the value. |
| `suffix(text: string): this` | `Field` | Static text rendered inside the control, after the value. |
| `withProps(extra: JsonObject): this` | `Field` | Merges arbitrary JSON-safe props onto the wire node (§7.3 custom fields). The escape hatch for props the typed DSL does not model; `props` contents are the protocol's open, additive-only surface (protocol §10). |
| `visibleOn(...contexts: FieldContextName[]): this` | `Field` | Restricts the field to the listed rendering contexts (§7.2). Replaces the previous allowlist. If `Field.hiddenOn` also names a context, the denylist wins; both are evaluated by `Field.appearsIn`. |
| `hiddenOn(...contexts: FieldContextName[]): this` | `Field` | Replaces the denylist of rendering contexts in which the field is removed (§7.2). |
| `readonlyOn(...modes: FormMode[]): this` | `Field` | Renders the field read-only in the listed form sub-modes (§7.2). A read-only field still renders and still validates, but is excluded from the fill set for that mode (§19: the value cannot be tampered in). |
| `disabled(): this` | `Field` | Statically disables the control (never fillable, never required). |
| `virtual(): this` | `Field` | Marks the field virtual (§7.2): it participates in the descriptor and validator but is NEVER filled onto the model. Its pruned value remains available to `BaseResource.beforeSave` and `BaseResource.afterSave`, where the resource may persist it. |
| `sensitive(): this` | `Field` | Marks the field's value a SECRET (TECH_SPEC §13 `payload` "secrets- scrubbed: fields may mark `sensitive()`"; §19). |
| `default(value: TValue): this` | `Field` | Default state value in `create` mode (protocol §7). |
| `dehydrate(fn: DehydrateFn<TValue>): this` | `Field` | Custom form-state → model-attribute mapping (§7.2). |
| `hydrate(fn: HydrateFn<TValue>): this` | `Field` | Custom model-record → form-state mapping (§7.2). |
| `required(when?: { field: string; matcher: ReactiveMatcher }): this` | `Field` | Marks the field required (§11.1). With a `when` matcher it compiles to a `requiredWhen` reactive rule instead, mirrored server-side (§11.2). |
| `nullable(): this` | `Field` | Allows `null` as a valid submitted value (§11.1). |
| `unique(opts: UniqueSpec = {}): this` | `Field` | Compiles to Vine's DB unique rule (§11.1) with automatic `whereNot(primaryKey, currentId)` in edit mode and tenant scoping when tenancy is active (§5.3). |
| `rules(fn: RulesFn): this` | `Field` | Replaces this field type's Vine base with a user-supplied schema (§7.2). |
| `maxLength(limit: number): this` | `Field` | Maximum string length; compiles to Vine `maxLength` (§11.1). |
| `minLength(limit: number): this` | `Field` | Minimum string length; compiles to Vine `minLength` (§11.1). |
| `visibleWhen(condition: ReactiveCondition): this` | `Field` | Declarative visibility rule (TECH_SPEC §7.2, the reactivity contract). Client-side the rule is cosmetic; the validator mirrors it server-side (§11.2). |
| `requiredWhen(condition: ReactiveCondition): this` | `Field` | Declarative requiredness rule; mirrored in the compiled validator even when the client UI is bypassed (the server-mirroring contract). |
| `disabledWhen(condition: ReactiveCondition): this` | `Field` | Declarative disabledness rule (the reactivity contract). A disabled field is never required and never filled (mirroring rule R5). |
| `sets( target: string, transform: SetsTransform, options: { source?: string; if?: JsonLogicRule } = {} ): this` | `Field` | Appends a declarative state derivation run when this field changes (protocol §5 `sets`), e.g. `.sets('slug', 'slugify')`. Derivations execute in declaration order. |
| `live(debounceMs: number = 300): this` | `Field` | Marks the field "live": dependent reactive rules re-run after local edits, debounced (protocol §5). Defaults to a 300 ms debounce. |
| `dependentOptions(resolver: OptionsResolver, dependsOn: string[] = []): this` | `Field` | Server-resolved dependent options (§7.2): the client refetches through this node's `urls.options` capability URL, sending the `dependsOn` state slice (protocol §4/§5). |
| `canSee(fn: CanSeeCallback): this` | `SchemaComponent` | Compile-time visibility gate (TECH_SPEC §7.2). When the callback returns `false` for the current request, the node is omitted from the compiled descriptor and its state key is stripped in the same pass. |
| `requiresAbility(ability: ResourceAbility): this` | `SchemaComponent` | Ability gate (TECH_SPEC §12, descriptor enforcement point): the node is OMITTED whenever the current request does not hold `ability` on the resource being compiled. |
| `visible(fn: CanSeeCallback): this` | `SchemaComponent` | Spelling of `SchemaComponent.canSee` used by the §7.1 layout DSL. Identical semantics — compile-time omission, never a `visible: false` flag on the wire (protocol §2 rule 3), and equally additive. |
| `recordDependent(): this` | `SchemaComponent` | Marks this node's subtree as recompiled per request (`VisibilityScope` `request`), never served from the §9.4 descriptor cache. |
| `columnSpan(span: number \| 'full'): this` | `SchemaComponent` | Width of this node inside a `columns(n)` layout parent; a `columnSpan` equal to the parent's `columns` (or the string `'full'`) spans the row (protocol §2 rule 5). |

### Introspection

Read by the descriptor compiler, the validator compiler and the table derivation. Useful
in tests; rarely called from a schema.

| Member | Declared on | Behaviour |
| --- | --- | --- |
| `get displayType(): string` | `Field` | Display counterpart of THIS instance (§7.4, protocol §8). Mirrors the class' static `displayType` — the `SchemaComponent.type` pattern — but as an instance member, because the projection of a choice field is a per-instance decision: `select`/`radio`/`checkbox-list` project to `badge-entry` only when their declared options carry colours, and to `text-entry` otherwise. The descriptor compiler reads THIS, never the static. |
| `detailProps(): JsonObject` | `Field` | Props THIS field's display counterpart needs in `detail` mode (protocol §8), as a fresh JSON-safe object. |
| `get columnType(): string \| undefined` | `Field` | Cell counterpart of THIS instance (§7.5), `undefined` when the field is not tabulatable. Instance-level for the same reason as `Field.displayType`: a coloured choice field derives a `badge` cell, an uncoloured one a `text` cell. Table derivation reads THIS. |
| `get columnKey(): string` | `Field` | Key of the index column this field derives to (§7.5), when it derives one at all. Defaults to the state key, which is right for every field whose value IS the column's value. |
| `get capabilities(): readonly string[]` | `Field` | Capability keys this field needs as `urls` entries on its wire node (protocol §4): `options` for anything whose option list the client has to ASK the server for, `upload` for the media types. Empty for a plain scalar field — a node with nothing dynamic to fetch carries no `urls` block at all, since optional protocol keys are never serialized as empty. |
| `contributeHints(_hints: EagerHints): void` | `Field` | Registers what this field needs eager-loaded before a detail/edit page reads it (§18), mirroring `Column.contributeHints` on the table side. A scalar field needs nothing; a relation field registers its relation, so hydrating `tags` into an id array costs zero per-record queries. |
| `appearsIn(context: FieldContextName): boolean` | `Field` | Whether this field appears in `context` under the static projection rules (§7.4). Independent of `canSee`, which is the request-dependent gate on `SchemaComponent`. |
| `isReadonlyIn(mode: FormMode): boolean` | `Field` | Whether the field renders read-only in the given form sub-mode (§7.2). |
| `get visibleContexts(): readonly FieldContextName[] \| undefined` | `Field` | Explicit `visibleOn` contexts, when declared. |
| `get hiddenContexts(): readonly FieldContextName[] \| undefined` | `Field` | Explicit `hiddenOn` contexts, when declared. |
| `get isVirtual(): boolean` | `Field` | Whether the field is virtual (never filled, §7.2). |
| `get isSensitive(): boolean` | `Field` | Whether the value is a secret (see `Field.sensitive`). |
| `nestedSchema(): readonly SchemaComponent[] \| undefined` | `Field` | The nested sub-form of a **state-boundary** field (§7.3 `repeater`), or `undefined` for every scalar field. |
| `boundaryKeys(): readonly string[]` | `Field` | Keys a **state boundary** owns ITSELF, as opposed to keys contributed by its item sub-form (§7.3). Empty for every field but `belongs-to-many`, whose items carry the relation key beside the pivot fields. |
| `get dehydrator(): DehydrateFn<TValue> \| undefined` | `Field` | The declared `dehydrate` mapper, when any. |
| `get hydrator(): HydrateFn<TValue> \| undefined` | `Field` | The declared `hydrate` mapper, when any. |
| `hydrateValue(record: unknown, ctx: HttpContextLike): TValue` | `Field` | Maps `record` onto this field's form state (§7.2): the author's `hydrate()` when declared, otherwise the field type's `Field.defaultHydrate`. This is the only entry point state builders should call — reading the attribute directly skips every per-type conversion. |
| `dehydrateValue( value: TValue, ctx: HttpContextLike, record?: unknown ): unknown \| Promise<unknown>` | `Field` | Maps a validated form value onto the model attribute (§7.2): the author's `dehydrate()` when declared, otherwise the field type's `Field.defaultDehydrate`. May be async — a user mapper is allowed to await (`dehydrate: (value, ctx) => unknown \| Promise`), so the fill pipeline awaits the result. |
| `get isNullable(): boolean` | `Field` | Whether `nullable()` was declared. |
| `get uniqueSpec(): UniqueSpec \| undefined` | `Field` | The declared unique constraint, when any. |
| `buildValidation(options: ValidationBuildOptions): SchemaTypes` | `Field` | Builds this field's Vine schema for one submission (§11.1). Called by the validator compiler, which owns optionality/grouping and never branches on field classes. |
| `get optionsResolver(): OptionsResolver \| undefined` | `Field` | The declared dependent-options resolver, when any. |
| `get refetchSchemaCauses(): readonly string[]` | `Field` | The DSL calls that forced `refetchSchema: true` on this field, in declaration order (e.g. `visibleWhen('status', <closure>)`). |
| `get reactiveSpec(): ReactiveSpec \| undefined` | `Field` | The declarative reactive spec for this field, or `undefined` when no reactive behavior was declared (the `reactive` key is then omitted from the wire node entirely — optional keys are never serialized as empty). |
| `get type(): string` | `SchemaComponent` | Registry key of this node on the wire (mirrors the static `type`). |
| `get visibilityScope(): VisibilityScope` | `SchemaComponent` | How this node's visibility relates to the §9.4 cache key — read by the descriptor compiler to decide whether the subtree can be memoized. The STRONGEST scope among the declared gates wins, so a node can only ever move further away from the cache as restrictions are added. |
| `maySee(ctx: HttpContextLike, record?: unknown): boolean` | `SchemaComponent` | Whether this node survives compilation for `ctx` (and `record` in edit/detail modes) — true only when EVERY declared gate passes. |
| `get props(): JsonObject` | `SchemaComponent` | Snapshot of the accumulated props (never `undefined` on the wire). |

## Coverage in the example app

From the committed codegen manifest of `examples/blog-admin`
(`.adonisjs/adonia/manifest.json`, TECH_SPEC §14). The generator asserts its
`protocolVersion` against the runtime constant, exactly as `adonia:doctor` does.

| Resource | Class | Panels | Schema fields | Index columns |
| --- | --- | --- | --- | --- |
| `activity-log` | `ActivityLogResource` | `admin`, `domain-admin` | `action`, `actorId`, `actorType`, `batchId`, `changes`, `createdAt`, `exception`, `panel`, `payload`, `resource`, `status`, `targetId`, `updatedAt` | `action`, `actorId`, `changes`, `createdAt`, `resource`, `status` |
| `categories` | `CategoryResource` | `admin`, `domain-admin` | `name`, `slug` | `name`, `posts`, `slug` |
| `comments` | `CommentResource` | `admin`, `domain-admin`, `tenant-admin` | `body`, `userId` | `body`, `userId` |
| `courses` | `CourseResource` | `admin`, `domain-admin` | `description`, `title` | `lessons`, `title` |
| `lessons` | `LessonResource` | `admin`, `domain-admin` | `content`, `position`, `title` | `position`, `title` |
| `posts` | `PostResource` | `admin`, `domain-admin`, `tenant-admin` | `authorId`, `body`, `categoryId`, `coverImage`, `publishedAt`, `slug`, `status`, `subtitle`, `tags`, `title` | `author.fullName`, `comments`, `publishedAt`, `slug`, `status`, `tags.name`, `title` |
| `tags` | `TagResource` | `admin`, `domain-admin` | `name`, `slug` | `name`, `slug` |
| `users` | `UserResource` | `admin`, `domain-admin`, `tenant-admin` | `email`, `fullName`, `role` | `email`, `fullName`, `role` |

Source: https://adonia.pages.dev/reference/fields/index.mdx
