Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/alineacms/alinea/llms.txt

Use this file to discover all available pages before exploring further.

Layout fields shape how entries are edited in the dashboard. They do not introduce new primitive data types — instead they compose other fields into repeating lists, nested objects, multi-tab layouts, and structured SEO blocks. Every layout field is accessed through the Field namespace.
Importing Field
import {Field} from 'alinea'

Field.list

Field.list creates a repeating list of rows where each row is an instance of one of the types you define in the schema option. Editors can add, reorder, and remove rows directly in the dashboard. The stored value is an array; each row carries an auto-assigned _id, _type, and fractional _index key for stable ordering.
Defining a list field
import {Config, Field} from 'alinea'

export const BlogPost = Config.document('Blog post', {
  fields: {
    sections: Field.list('Page sections', {
      schema: {
        // A plain text section
        TextSection: Config.type('Text section', {
          fields: {
            heading: Field.text('Heading', {required: true}),
            body: Field.richText('Body', {searchable: true})
          }
        }),
        // An image section
        ImageSection: Config.type('Image section', {
          fields: {
            image: Field.image('Image'),
            caption: Field.text('Caption', {width: 0.75})
          }
        })
      }
    })
  }
})

Pre-populating with an initial value

List field with initial rows
import {Config, Field} from 'alinea'

export const Page = Config.document('Page', {
  fields: {
    items: Field.list('Items', {
      schema: {
        Item: Config.type('Item', {
          fields: {title: Field.text('Title')}
        })
      },
      initialValue: [
        {_type: 'Item', title: 'First item'},
        {_type: 'Item', title: 'Second item'}
      ]
    })
  }
})

Programmatic editing with Edit.list

Use Edit.list when creating or updating entries programmatically (for example, in a seed script or migration):
Edit.list usage
import {Edit, Field} from 'alinea'

const sectionsField = /* your list field */ undefined as any

const newSections = Edit.list(sectionsField)
  .insertAt(0, 'TextSection', {heading: 'Introduction', body: []})
  .value()
schema
Schema
required
A Record<string, Type> defining the allowed row types. Each key is the _type discriminant stored with each row. At least one type must be provided.
initialValue
Array
Pre-populated rows for new entries. Each element must include a _type key matching one of the schema type names.
help
ReactNode
Instructional text below the field label.
inline
boolean
Compact, borderless rendering of the list field.
width
number
Fractional form width (0–1).
hidden
boolean
Hides the list from the dashboard.
validate
(value: Array<Row>) => boolean | string | undefined
A custom validation function run against the full row array.

Field.object

Field.object groups a set of fields into a single nested object. The stored value is a plain object whose shape matches the fields you define. Unlike Field.list, an object field always contains exactly one instance of its fields — no repeating rows.
Defining an object field
import {Config, Field} from 'alinea'

export const ProductPage = Config.document('Product page', {
  fields: {
    pricing: Field.object('Pricing', {
      fields: {
        amount: Field.number('Amount', {minValue: 0}),
        currency: Field.select('Currency', {
          options: {usd: 'USD', eur: 'EUR', gbp: 'GBP'},
          initialValue: 'usd',
          width: 0.5
        }),
        showSalePrice: Field.check('Show sale price')
      }
    })
  }
})
TypeScript infers the shape of the nested value automatically — in the example above, entry.pricing is typed as { amount: number | null; currency: 'usd' | 'eur' | 'gbp'; showSalePrice: boolean }.
fields
FieldsDefinition
required
A Record<string, Field> defining the nested fields. The result type is inferred from these definitions.
help
ReactNode
Instructional text below the label.
width
number
Fractional form width (0–1).
inline
boolean
Renders the object fields without extra chrome.

Field.tabs

Field.tabs groups fields into tab panels in the dashboard. You pass a list of Field.tab(...) definitions; the resulting section is spread into the parent fields object with the ... spread operator.
Tabs only affect how fields are displayed in the dashboard. The data structure is completely flat — querying an entry gives you all fields at the same level regardless of which tab they belong to. There is no nesting in the stored or queried shape.
Using Field.tabs
import {Config, Field} from 'alinea'

export const Article = Config.document('Article', {
  fields: {
    // Fields outside of tabs appear above the tab bar
    title: Field.text('Title', {required: true}),

    ...Field.tabs(
      Field.tab('Content', {
        fields: {
          body: Field.richText('Body text', {searchable: true}),
          summary: Field.text('Summary', {multiline: true})
        }
      }),
      Field.tab('Settings', {
        fields: {
          publishedAt: Field.date('Published at'),
          featured: Field.check('Featured', {
            description: 'Show in featured sections'
          })
        }
      }),
      Field.tab('SEO', {
        fields: {
          metadata: Field.metadata()
        }
      })
    )
  }
})
Field.tab is an alias for Config.type — it accepts the same label string and { fields } object. Field.tabs is variadic and accepts any number of tab definitions.

Field.metadata

Field.metadata is a pre-built structured field that covers the most common SEO metadata fields. It renders a composite panel with title, description, and Open Graph image and text. The stored value implements the Metadata interface.
Using Field.metadata
import {Config, Field} from 'alinea'

export const LandingPage = Config.document('Landing page', {
  fields: {
    title: Field.text('Title'),
    body: Field.richText('Body'),
    meta: Field.metadata('SEO metadata', {
      inferTitleFrom: 'title'
    })
  }
})
The Metadata type:
Metadata type shape
interface Metadata {
  title: string
  description: string
  openGraph: {
    image: ImageLink
    title: string
    description: string
  }
}
label
string
Field label shown in the dashboard. Defaults to "Metadata".
inferTitleFrom
string
Key of another field in the same type to use as the default metadata title.
inferDescriptionFrom
string
Key of another field in the same type to use as the default metadata description.
inferImageFrom
string
Key of another field in the same type to use as the default Open Graph image.

Full layout example

Combined layout fields
import {Config, Field} from 'alinea'

export const FullPage = Config.document('Full page', {
  fields: {
    title: Field.text('Title', {required: true}),

    hero: Field.object('Hero', {
      fields: {
        image: Field.image('Image'),
        headline: Field.text('Headline'),
        cta: Field.link('CTA link')
      }
    }),

    sections: Field.list('Content sections', {
      schema: {
        Text: Config.type('Text', {
          fields: {
            body: Field.richText('Body', {searchable: true})
          }
        }),
        Image: Config.type('Image', {
          fields: {image: Field.image('Image')}
        })
      }
    }),

    ...Field.tabs(
      Field.tab('Settings', {
        fields: {
          publishedAt: Field.date('Published at', {width: 0.5}),
          featured: Field.check('Featured')
        }
      }),
      Field.tab('SEO', {
        fields: {meta: Field.metadata()}
      })
    )
  }
})

Build docs developers (and LLMs) love