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:
valuefor the current fieldsiblingDatafor fields at the same nesting leveldatafor the whole formsetValuefor 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.
The recommended split
- 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
beforeChangeand adminonChangewhen 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.
Related pages
- Hooks overview for the shared lifecycle model.
- Cloud-safe hooks for the Cloud-specific subset and sync behavior.
- Collection hooks when the logic grows beyond one field.
- Fields overview for the rest of the field config surface.
- Hook context for the
user,req,doc, anddatavalues these hooks receive.