Dyrecteddyrected
Core Concepts

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

CategoryTypes
Texttext, textarea, richText, email, url, icon
Number & booleannumber, boolean
Date & timedate, datetime, time
Selectionselect, multiSelect, radio
Relationshipsrelationship, join
Structuralobject, array, blocks
Layoutrow
Mediaimage
Freeformjson

For full options on each type see the Field Types Reference.


Base properties every field shares

These properties apply to every field type:

PropertyTypeDefaultWhat it does
namestringrequiredThe key in the database and in JSON responses. Use camelCase.
typeFieldTyperequiredWhich field type to use.
labelstringtitle-cased nameThe label shown in the Admin UI form.
requiredbooleanfalseEnforced on both client (Admin UI) and server (API validation).
uniquebooleanfalseAdds a database-level unique constraint. Requires a schema sync.
defaultValueanyundefinedValue used for new documents. Evaluated server-side.
promotedbooleanfalseExtracts this field from the JSON blob into its own indexed SQL column.
renameTostringLazy migration — read from this old key if the new name is missing.
adminobjectUI-only options: placeholder, description, hidden, condition, readOnly.
accessobjectField-level read and update access functions.
hooksobjectbeforeChange 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.

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 slug

A 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

On this page