164 lines
5.7 KiB
Markdown
164 lines
5.7 KiB
Markdown
---
|
|
title: "Field Hooks"
|
|
aliases: [field-hooks, payload-field-hooks]
|
|
tags: [payloadcms, hooks, fields, lifecycle]
|
|
sources: [raw/hooks__fields.md]
|
|
created: 2026-05-15
|
|
updated: 2026-05-15
|
|
---
|
|
|
|
Field Hooks run on individual fields during Document lifecycle events — allowing isolated, per-field logic separate from [[wiki/payloadcms/hooks-collections|Collection Hooks]].
|
|
|
|
## Config
|
|
|
|
Add to any field's `hooks` property:
|
|
|
|
```ts
|
|
const field: Field = {
|
|
name: 'username',
|
|
type: 'text',
|
|
hooks: {
|
|
beforeValidate: [(args) => { ... }],
|
|
beforeChange: [(args) => { ... }],
|
|
beforeDuplicate:[(args) => { ... }],
|
|
afterChange: [(args) => { ... }],
|
|
afterRead: [(args) => { ... }],
|
|
},
|
|
}
|
|
```
|
|
|
|
Each hook type accepts an **array** of sync or async functions. The return value replaces the field value for that operation.
|
|
|
|
> **GraphQL warning:** Changing the *type* of data returned from a field breaks GraphQL. Use [[wiki/payloadcms/hooks-collections|Collection Hooks]] for type changes.
|
|
|
|
## Hook Types
|
|
|
|
| Hook | Runs | Use for |
|
|
|------|------|---------|
|
|
| `beforeValidate` | create + update, after client validation | Normalise/format input before server validation |
|
|
| `beforeChange` | create + update, before validation | Transform or gate data; data is unvalidated here |
|
|
| `afterChange` | create + update, after save | Side effects, logging, downstream triggers |
|
|
| `afterRead` | read operations | Format for output (dates, masks, derived values) |
|
|
| `beforeDuplicate` | document duplication | Avoid unique constraint violations; runs before `beforeValidate` |
|
|
|
|
### Hook Execution Order (create/update)
|
|
|
|
1. `beforeValidate` (client → server)
|
|
2. `validate` (server)
|
|
3. `beforeChange`
|
|
4. DB write
|
|
5. `afterChange`
|
|
|
|
On read: `afterRead`.
|
|
On duplicate: `beforeDuplicate` → `beforeValidate` → `beforeChange`.
|
|
|
|
## Arguments Reference
|
|
|
|
All field hooks receive the same args object:
|
|
|
|
| Arg | Type | Notes |
|
|
|-----|------|-------|
|
|
| `value` | any | Current field value |
|
|
| `data` | Partial\<Doc\> | Incoming data (create/update) or full doc (afterRead) |
|
|
| `originalDoc` | Doc | Doc before changes (update); resulting doc (afterChange) |
|
|
| `previousDoc` | Doc | Doc before changes in `afterChange` |
|
|
| `previousValue` | any | Previous field value — `beforeChange` and `afterChange` only |
|
|
| `siblingData` | object | Adjacent field values |
|
|
| `siblingFields` | Field[] | Adjacent field definitions |
|
|
| `previousSiblingDoc` | object | Sibling data before changes — `beforeChange`/`afterChange` only |
|
|
| `siblingDocWithLocales` | object | Sibling data with all locales |
|
|
| `operation` | string | `'create'` \| `'update'` — useful for branching logic |
|
|
| `field` | Field | Field definition |
|
|
| `path` | string[] | Runtime path |
|
|
| `schemaPath` | string[] | Schema-level path |
|
|
| `collection` | Collection \| null | null if field belongs to a Global |
|
|
| `global` | Global \| null | null if field belongs to a Collection |
|
|
| `findMany` | boolean | `afterRead` only — differentiates find-one vs find-many |
|
|
| `overrideAccess` | boolean | Whether access control is bypassed |
|
|
| `context` | object | Shared context across hooks — see [[wiki/payloadcms/hooks-context|Hooks Context]] |
|
|
| `req` | Request | Web request (mocked for Local API) |
|
|
|
|
## Common Patterns
|
|
|
|
### Normalise input (beforeValidate)
|
|
```ts
|
|
beforeValidate: [
|
|
({ value }) => value?.trim().toLowerCase(),
|
|
],
|
|
```
|
|
|
|
### Branch on operation (beforeChange)
|
|
```ts
|
|
beforeChange: [
|
|
({ value, operation }) => {
|
|
if (operation === 'create') { /* extra logic */ }
|
|
return value
|
|
},
|
|
],
|
|
```
|
|
|
|
### Log on change (afterChange)
|
|
```ts
|
|
afterChange: [
|
|
({ value, previousValue, req }) => {
|
|
if (value !== previousValue) {
|
|
console.log(`Changed: ${previousValue} → ${value}`)
|
|
}
|
|
},
|
|
],
|
|
```
|
|
|
|
### Format for display (afterRead)
|
|
```ts
|
|
afterRead: [
|
|
({ value }) => new Date(value).toLocaleDateString(),
|
|
],
|
|
```
|
|
|
|
### Deduplicate on copy (beforeDuplicate)
|
|
```ts
|
|
beforeDuplicate: [
|
|
({ value }) => (value ?? 0) + 1,
|
|
],
|
|
```
|
|
Default behaviour for text fields: Payload appends `" - Copy"` unless you define your own `beforeDuplicate` hook.
|
|
|
|
## TypeScript
|
|
|
|
```ts
|
|
import type { FieldHook } from 'payload'
|
|
import type { Post } from '@/payload-types'
|
|
|
|
// Three generics: <DocumentType, ValueType, SiblingDataType>
|
|
type PostTitleHook = FieldHook<Post, string, Post>
|
|
|
|
const slugifyTitle: PostTitleHook = ({ value, siblingData }) => {
|
|
if (!siblingData.slug && value) {
|
|
return value.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-')
|
|
}
|
|
return value
|
|
}
|
|
```
|
|
|
|
Use all three generics for full autocomplete on `value`, `data`, `siblingData`, and `originalDoc`.
|
|
|
|
## Key Takeaways
|
|
|
|
- Field hooks are the preferred place to **isolate per-field logic** — keeps Collection hooks clean.
|
|
- `operation` arg lets you branch `beforeChange`/`afterChange` between create and update.
|
|
- `beforeChange` receives **unvalidated** data — validate manually if using for side effects.
|
|
- Changing the return type breaks GraphQL — stick to same type or use Collection hooks.
|
|
- `beforeDuplicate` runs **before** `beforeValidate`; default appends `" - Copy"` to unique text fields.
|
|
- Use three-generic `FieldHook<Doc, Val, Sibling>` for full TypeScript safety.
|
|
- `context` (from `req.context`) lets hooks share data without extra DB fetches — see [[wiki/payloadcms/hooks-context|Hooks Context]].
|
|
|
|
## Related
|
|
|
|
- [[wiki/payloadcms/hooks-collections|Collection Hooks]] — document-level lifecycle hooks
|
|
- [[wiki/payloadcms/hooks-context|Hooks Context (req.context)]] — share state across hooks
|
|
- [[wiki/payloadcms/hooks|Hooks Overview]] — hook categories and execution model
|
|
- [[wiki/payloadcms/fields-overview|Fields Overview]] — field config, validation, custom components
|
|
|
|
## Sources
|
|
|
|
- `raw/hooks__fields.md` — https://payloadcms.com/docs/hooks/fields
|