Dyrected
Deployment & OperationsServer RuntimeHooks

Fields

Use field hooks for per-value transforms on the server, and admin hooks for reactive form behavior in the dashboard.

Field hooks are the smallest hook surface in Dyrected. They let you attach logic to one value instead of to the whole document, which keeps common transforms close to the field they belong to.

This page covers both sides of field-level behavior:

  • Server field hooks for values that must be normalized or reshaped on every API path
  • Admin field hooks for reactive editor behavior inside the dashboard

By the end of this page, you should know when field hooks are the right fit, when to move up to a collection or global hook, and what the admin-only hooks can and cannot do.

If you are targeting Cloud, read Cloud-safe hooks alongside this page. That page is the canonical guide for which field hook surfaces survive sync:schema.

Server field hooks

Server field hooks live directly on a field's hooks key:

{
  name: 'email',
  type: 'email',
  hooks: {
    beforeChange: [],
    afterRead: [],
  },
}

These hooks run on the server and apply even when the write or read does not come from the admin UI.

beforeChange

beforeChange receives the current field value, the full incoming data, the originalDoc on updates, the authenticated user, and a read-only database adapter.

Use it when the stored value should always be normalized the same way:

{
  name: 'email',
  type: 'email',
  hooks: {
    beforeChange: [
      ({ value }) => typeof value === 'string' ? value.trim().toLowerCase() : value,
    ],
  },
}

Because this hook runs inside nested object, array, and blocks fields too, it is a strong fit for value-level cleanup that should happen everywhere that field appears.

beforeChange also supports a declarative string form, which is the Cloud-safe option for field-level transforms:

{
  name: 'email',
  type: 'email',
  hooks: {
    beforeChange: [
      "value != null ? value + '!' : value",
    ],
  },
}

For the exact declarative beforeChange context in Cloud-safe mode, see Cloud-safe hooks.

afterRead

afterRead receives the stored value, the full returned doc, the authenticated user, and a read-only database adapter.

Use it when the stored value is correct, but the returned value should be transformed:

{
  name: 'apiKey',
  type: 'text',
  hooks: {
    afterRead: [
      ({ value, user }) => user?.roles?.includes('admin') ? value : '••••••••',
    ],
  },
}

afterRead is still function-only in v1. If you need a Cloud-safe post-read transform, use collection or global afterRead instead.

When field hooks beat collection hooks

Choose a field hook when:

  • the logic belongs to one value
  • the same transform should apply recursively in nested structures
  • the document-level hook would mostly exist just to target one field

Move up to a collection hook or global hook when the logic depends on several fields, the operation, or post-write side effects.

Admin field hooks

Admin hooks live under field.admin.hooks and run in the browser inside the dashboard form. They improve editor experience, but they do not replace server hooks for enforcement. They do not run for direct SDK or REST writes.

onChange

admin.hooks.onChange recalculates a field's value when sibling values change.

{
  name: 'slug',
  type: 'text',
  admin: {
    hooks: {
      onChange: ({ siblingData }) =>
        String(siblingData.title ?? '')
          .toLowerCase()
          .trim()
          .replace(/[^a-z0-9]+/g, '-')
          .replace(/(^-|-$)/g, ''),
    },
  },
}

It receives:

  • value for the current field
  • siblingData for fields at the same nesting level
  • data for the whole form
  • setValue for imperative updates when the logic is async or multi-step

Use it for slug generation, computed totals, or other immediate form feedback.

When you want the same admin behavior to survive Cloud schema sync, use the declarative string form instead of a function:

{
  name: 'slug',
  type: 'text',
  admin: {
    hooks: {
      onChange: "siblingData.title != null ? siblingData.title : value",
    },
  },
}

The declarative form always returns the next field value. It does not support imperative setValue. If you need async or multi-step UI behavior, keep using the function form and treat it as dashboard-only behavior.

For the exact declarative admin.hooks.onChange context in Cloud-safe mode, see Cloud-safe hooks.

When the schema is sent to the admin app over /api/schemas, Dyrected preserves declarative onChange strings as expressions and still serializes function onChange hooks for the form sandbox. That is useful to know when you are debugging admin behavior, but it does not change the core rule: these hooks are a dashboard feature, not a server guarantee.

options

admin.hooks.options is available on select, multiSelect, and radio fields. It recalculates the available choices from the current form state.

{
  name: 'state',
  type: 'select',
  options: [],
  admin: {
    hooks: {
      options: ({ siblingData }) => {
        if (siblingData.country === 'us') {
          return [
            { label: 'California', value: 'CA' },
            { label: 'New York', value: 'NY' },
          ]
        }

        if (siblingData.country === 'ca') {
          return [
            { label: 'Ontario', value: 'ON' },
            { label: 'Quebec', value: 'QC' },
          ]
        }

        return []
      },
    },
  },
}

Two practical details matter here:

  • The hook can be async.
  • If the current selection is no longer valid under the new option list, Dyrected clears it automatically.

admin.hooks.options is still function-only in v1. It is not part of the Cloud-safe declarative subset yet.

  • Put must-always-hold logic in server hooks.
  • Put editor convenience logic in admin hooks.
  • Use both when you want fast feedback in the form and guaranteed enforcement on the server.
  • Prefer declarative strings on field beforeChange and admin onChange when you want those transforms to survive Cloud schema sync.

For example, an admin onChange hook can keep a slug field up to date as the editor types, while a server beforeChange hook still normalizes the final stored slug for API writes and imports.

On this page

Dyrected| Cloud

Get your backend ready in minutes

Use a managed database, storage, APIs, and admin dashboard without setting up the infrastructure yourself.

Set Up My Backend