Fields
How fields define the shape of your content — the types available, the properties they share, and how structural fields compose complex documents.
A field is the basic unit of content in Dyrected. Every collection and global is made up of fields. Together they define the database schema, the REST API response shape, and the Admin UI form — all at once.
defineCollection({
slug: 'posts',
fields: [
{ name: 'title', label: 'Title', type: 'text', required: true },
{ name: 'body', label: 'Body', type: 'richText' },
{ name: 'status', label: 'Status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' },
{ name: 'author', label: 'Author', type: 'relationship', relationTo: 'users' },
{ name: 'publishedAt', label: 'Published At', type: 'datetime' },
],
})Available field types
| Category | Types |
|---|---|
| Text | text, textarea, richText, email, url, icon |
| Number & boolean | number, boolean |
| Date & time | date, datetime, time |
| Selection | select, multiSelect, radio |
| Relationships | relationship, join |
| Structural | object, array, blocks |
| Layout | row |
| Media | image |
| Freeform | json |
For full options on each type see the Field Types Reference.
Base properties every field shares
These properties apply to every field type:
| Property | Type | Default | What it does |
|---|---|---|---|
name | string | required | The key in the database and in JSON responses. Use camelCase. |
type | FieldType | required | Which field type to use. |
label | string | title-cased name | The label shown in the Admin UI form. |
required | boolean | false | Enforced on both client (Admin UI) and server (API validation). |
unique | boolean | false | Adds a database-level unique constraint. Requires a schema sync. |
defaultValue | any | undefined | Value used for new documents. Evaluated server-side. |
promoted | boolean | false | Extracts this field from the JSON blob into its own indexed SQL column. |
renameTo | string | — | Lazy migration — read from this old key if the new name is missing. |
admin | object | — | UI-only options: placeholder, description, hidden, condition, readOnly. |
access | object | — | Field-level read and update access functions. |
hooks | object | — | beforeChange and afterRead hooks at the field level. |
required vs unique
required is enforced by validation — submitting a document without a required field returns a 400 with a validation error. unique is a database constraint — inserting a duplicate value returns a 409 Conflict.
promoted and database indexes
By default, every field value lives inside a JSON blob column. Setting promoted: true extracts the field into its own SQL column, which means the database can index it and queries using that field in where filters or sort are significantly faster. Good candidates: slug, status, email, publishedAt. See Schema Definition.
Structural fields
Structural fields let you compose nested and repeating data inside a single document.
object — a fixed group of sub-fields
Use object when a document has a named section with a fixed set of sub-fields.
{
name: 'seo',
type: 'object',
fields: [
{ name: 'title', label: 'Title', type: 'text' },
{ name: 'description', label: 'Description', type: 'textarea' },
{ name: 'image', label: 'Image', type: 'relationship', relationTo: 'media' },
],
}
// Stored as: { seo: { title: '...', description: '...', image: 'media-id' } }array — a repeating group of sub-fields
Use array when a document can have zero or more instances of the same structure.
{
name: 'testimonials',
type: 'array',
fields: [
{ name: 'author', label: 'Author', type: 'text' },
{ name: 'quote', label: 'Quote', type: 'textarea' },
{ name: 'rating', label: 'Rating', type: 'number' },
],
}
// Stored as: { testimonials: [{ author: '...', quote: '...', rating: 5 }, ...] }blocks — a repeating group with variable types
Use blocks when each item in a list can be a different type. This is the foundation of page builders.
{
name: 'content',
type: 'blocks',
blocks: [
{
slug: 'hero',
fields: [
{ name: 'heading', label: 'Heading', type: 'text' },
{ name: 'subheading', label: 'Subheading', type: 'text' },
{ name: 'cta', label: 'Cta', type: 'text' },
],
},
{
slug: 'richText',
fields: [
{ name: 'body', label: 'Body', type: 'richText' },
],
},
{
slug: 'image',
fields: [
{ name: 'image', label: 'Image', type: 'relationship', relationTo: 'media' },
{ name: 'caption', label: 'Caption', type: 'text' },
],
},
],
}
// Each item in the array has a `blockType` property identifying its slugA block may also declare variants — a set of approved layouts that share the same fields. The chosen variant is stored on each item under the reserved variant key. See Presentation variants.
See the Building a Page Builder guide for a full walkthrough.
row — layout only, no data
row groups fields side by side in the Admin UI form. It has no effect on the API or database.
{
type: 'row',
fields: [
{ name: 'firstName', label: 'First Name', type: 'text' },
{ name: 'lastName', label: 'Last Name', type: 'text' },
],
}Field-level access and hooks
Every field can define its own access rules and hooks independently of the collection:
{
name: 'internalNotes',
type: 'textarea',
access: {
read: ({ user }) => user?.role === 'admin', // stripped from response if false
update: ({ user }) => user?.role === 'admin', // ignored on write if false
},
hooks: {
beforeChange: [({ value }) => value?.trim()],
afterRead: [({ value }) => value ?? ''],
},
}Field access is evaluated after collection-level access is granted. See Access Control for the full model.
Next steps
- Field Types Reference — every field type and all its options
- Relationships — how
relationshipandjoinfields link collections - Schema Definition — how fields map to database columns and how migrations work
- Building a Page Builder — using
blocksfor flexible layouts