---
title: "PayloadCMS — Array Field"
aliases: [payload-array-field, array-field-payload]
tags: [payloadcms, fields, array, cms, typescript]
sources: [raw/fields__array.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

The Array Field stores repeating sets of fields as an array of objects. Each row contains the same field structure you define. Arrays can be nested inside other arrays for infinitely deep data structures.

**Common use cases:**
- Image slider (upload + caption text)
- Navigation items (relationship + checkbox "open in new tab")
- Event agenda timeslots (date + label + relationship)

```ts
import type { Field } from 'payload'

export const MyArrayField: Field = {
  name: 'slider',
  type: 'array',
  fields: [
    { name: 'title', type: 'text' },
    { name: 'image', type: 'upload', relationTo: 'media', required: true },
    { name: 'caption', type: 'text' },
  ],
}
```

## Config Options

| Option | Required | Description |
|--------|----------|-------------|
| `name` | yes | DB property name |
| `fields` | yes | Field definitions for each row |
| `minRows` | no | Minimum row count during validation |
| `maxRows` | no | Maximum row count during validation |
| `labels` | no | Customize `singular`/`plural` row labels in Admin |
| `defaultValue` | no | Array of row objects as default |
| `localized` | no | Localizes entire array (no need to localize each sub-field) |
| `required` | no | Field must have a value |
| `validate` | no | Custom validation function (runs client + server) |
| `saveToJWT` | no | Include in JWT if top-level in auth collection |
| `hooks` | no | Field lifecycle hooks |
| `access` | no | Field-level access control |
| `hidden` | no | Hide from APIs/Admin but still save to DB |
| `interfaceName` | no | Reusable TypeScript interface + GraphQL type name |
| `dbName` | no | Custom Postgres table name (auto-generated otherwise) |
| `virtual` | no | Disable DB persistence or link to a relationship path |
| `typescriptSchema` | no | Override generated TS type with a JSON schema |

## Admin Options

```ts
admin: {
  initCollapsed: true,        // start rows collapsed
  isSortable: false,          // disable drag-to-reorder (default: true)
  components: {
    RowLabel: './RowLabel',   // custom React component per row
  },
}
```

### Custom Row Label

```tsx
'use client'
import { useRowLabel } from '@payloadcms/ui'

export const ArrayRowLabel = () => {
  const { data, rowNumber } = useRowLabel<{ title?: string }>()
  return <div>{data.title || 'Slide'} {String(rowNumber).padStart(2, '0')}</div>
}
```

## Gotchas

### `unique: true` on nested fields
Setting `unique: true` on a field **inside** an array creates a **collection-wide** unique index — not per-document. Two documents cannot share the same value at that nested path. On MongoDB, documents without the array collide on `null`.

**Fix:** Use a custom `validate` function on the array field to enforce uniqueness within a single document's rows.

### Localization
When `localized: true` on the array itself, all sub-fields are localized automatically. No need to mark each nested field.

## Full Example

```ts
import type { CollectionConfig } from 'payload'

export const ExampleCollection: CollectionConfig = {
  slug: 'example-collection',
  fields: [
    {
      name: 'slider',
      type: 'array',
      label: 'Image Slider',
      minRows: 2,
      maxRows: 10,
      interfaceName: 'CardSlider',
      labels: { singular: 'Slide', plural: 'Slides' },
      fields: [
        { name: 'title', type: 'text' },
        { name: 'image', type: 'upload', relationTo: 'media', required: true },
        { name: 'caption', type: 'text' },
      ],
    },
  ],
}
```

## Custom Components

### Server Component (Field)
```tsx
import { ArrayField } from '@payloadcms/ui'
import type { ArrayFieldServerComponent } from 'payload'

export const CustomArrayFieldServer: ArrayFieldServerComponent = ({
  clientField, path, schemaPath, permissions,
}) => <ArrayField field={clientField} path={path} schemaPath={schemaPath} permissions={permissions} />
```

### Client Component (Field)
```tsx
'use client'
import { ArrayField } from '@payloadcms/ui'
import type { ArrayFieldClientComponent } from 'payload'

export const CustomArrayFieldClient: ArrayFieldClientComponent = (props) => <ArrayField {...props} />
```

## Key Takeaways

- Use `type: 'array'` with a `fields` array to define repeating row structure
- `minRows` / `maxRows` add validation bounds without custom code
- `localized: true` on the array localizes all sub-fields — don't set it per sub-field
- `unique: true` inside an array = collection-wide DB index, not per-row uniqueness; use `validate` instead
- `interfaceName` generates a reusable TypeScript interface for the row shape
- Custom `RowLabel` via `admin.components.RowLabel` + `useRowLabel` hook for dynamic row headings
- Arrays can be nested inside arrays for complex nested data

## Related

- [[wiki/payloadcms/fields-complex|Fields: Complex]] — all complex field types overview
- [[wiki/payloadcms/fields-basic|Fields: Basic]] — scalar field reference
- [[wiki/payloadcms/database-indexes|Database Indexes]] — unique index behavior and gotchas
- [[wiki/payloadcms/localization|Localization]] — how `localized: true` works across field types
- [[wiki/payloadcms/typescript|TypeScript]] — `interfaceName` and type generation
- [[wiki/payloadcms/hooks|Hooks]] — field lifecycle hooks

## Sources

- `raw/fields__array.md` — https://payloadcms.com/docs/fields/array
