---
title: "has-many"
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.

# has-many

<!-- Generated by `pnpm docs:fields` from packages/core/src/schema/fields/has_many.ts. Edit the TSDoc, not this file. -->

A repeater whose visible submitted rows are authoritative for one Lucid
has-many relation. Omission is handled by the fill set and performs no write.

| | |
| --- | --- |
| Builder | `F.hasMany(relation: string, schema: readonly SchemaComponent[] = []): HasMany` |
| Wire type key | `has-many` |
| Class chain | `HasMany` → `Repeater` → `Field` |
| Detail projection | `repeater-entry` |
| Column counterpart | — *(not tabulatable)* |

## Example

```ts
import { F } from '@adonia/core'

F.hasMany('attribute')
```

## Props

Beyond the shared [field DSL](/reference/fields#the-shared-field-dsl):

| Prop | Declared on | Behaviour |
| --- | --- | --- |
| `relation(name: string): this` | `HasMany` | Changes the parent model relation used for hydration, scope, and persistence. |
| `keyType(type: 'number' \| 'string'): this` | `HasMany` | Selects the Vine/ownership id type accepted in submitted child rows. |
| `min(count: number): this` | `Repeater` | Minimum number of rows; compiles to the array's `minLength` (§11.1). |
| `max(count: number): this` | `Repeater` | Maximum number of rows; compiles to the array's `maxLength` (§11.1). |
| `reorderable(): this` | `Repeater` | Lets the user drag rows into a new order (order is the array order). |
| `collapsible(): this` | `Repeater` | Lets the user fold individual rows away. |
| `itemLabel(source: string \| ItemLabelFn): this` | `Repeater` | Names each row. |
| `table(): this` | `Repeater` | Compact layout: rows render as table lines rather than stacked cards. A rendering hint only — the state shape and the validator are identical either way. |
| `defaultItems(count: number): this` | `Repeater` | Pre-populates `count` blank rows in `create` mode (protocol §7): the node's `default` becomes an array of `Repeater.blankItem`s, so a form that always has at least one row does not make the user press "add" first. |

### Introspection

Read-only members this type adds — used by the compiler, the table derivation and
tests rather than by a schema author.

| Member | Declared on | Behaviour |
| --- | --- | --- |
| `boundaryKeys(): readonly string[]` | `HasMany` | Preserves child identity and lock snapshots across nested validation. |
| `contributeHints(hints: EagerHints): void` | `HasMany` | Preloads the declared relation once so hydration never becomes per-row I/O. |
| `detailProps(): JsonObject` | `HasMany` | Carries the resolved relation name into the detail projection. |
| `hydrateOptimisticLocks( record: unknown, value: unknown, ctx: HttpContextLike ): Promise<void>` | `HasMany` | Adds the child resource's lock snapshot after ordinary synchronous form hydration. Resource lookup is lazy and therefore must happen in this explicit asynchronous pass. |
| `persistRelation(record: object, value: unknown, ctx: HttpContextLike): Promise<void>` | `HasMany` | Reconciles the submitted rows against children owned by the active parent. |
| `nestedSchema(): readonly SchemaComponent[]` | `Repeater` | The item sub-form (protocol §2 `children` of this node). Declared here rather than as a `LayoutComponent`'s `children` because this is a STATE boundary: the descriptor compiler emits the sub-form as children but stops collecting `stateKeys`, and `collectFields` never descends. |
| `blankItem(): RepeaterItem` | `Repeater` | A fresh empty row: every child key present, holding its declared `default` or explicit `null`. |
| `labelFor(item: RepeaterItem, index: number): string` | `Repeater` | The human name of one row, server-side: the `itemLabel` closure when declared, otherwise the named child key's value, otherwise the ordinal. |

## Validation

Vine base: `vine.string()`, `vine.number()`.

Contributed by `HasMany.vineSchema()` in `packages/core/src/schema/fields/has_many.ts`. The shared modifiers (`required`, `nullable`, `requiredWhen`, `unique`, optionality) are applied on top by `Field.buildValidation`, and a `rules()` override replaces the base entirely — the validator compiler never branches on the field class.

## Declared in

- `packages/core/src/schema/fields/has_many.ts` — `HasMany`
- `packages/core/src/schema/fields/repeater.ts` — `Repeater`

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