Field Hooks

Field-level hooks offer incredible potential for encapsulating your logic. They help to isolate concerns and package up functionalities to be easily reusable across your projects.

Example use cases include:

  • Automatically add an owner relationship to a Document based on the req.user.id
  • Encrypt / decrypt a sensitive field using beforeValidate and afterRead hooks
  • Auto-generate field data using a beforeValidate hook
  • Format incoming data such as kebab-casing a document slug with beforeValidate
  • Restrict updating a document to only once every X hours using the beforeChange hook

All field types provide the following hooks:

Config

Example field configuration:

1
import { Field } from 'payload/types';
2
3
const ExampleField: Field = {
4
name: 'name',
5
type: 'text',
6
hooks: {
7
beforeValidate: [(args) => {...}],
8
beforeChange: [(args) => {...}],
9
afterChange: [(args) => {...}],
10
afterRead: [(args) => {...}],
11
}
12
}

Arguments and return values

All field-level hooks are formatted to accept the same arguments, although some arguments may be undefined based on which field hook you are utilizing.

Arguments

Field Hooks receive one args argument that contains the following properties:

OptionDescription
dataThe data passed to update the document within create and update operations, and the full document itself in the afterRead hook.
siblingDataThe sibling data passed to a field that the hook is running against.
findManyBoolean to denote if this hook is running against finding one, or finding many within the afterRead hook.
operationA string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations.
originalDocThe full original document in update operations. In the afterChange hook, this is the resulting document of the operation.
previousDocThe document before changes were applied, only in afterChange hooks.
previousSiblingDocThe sibling data of the document before changes being applied, only in beforeChange and afterChange hook.
reqThe Express request object. It is mocked for Local API operations.
valueThe value of the field.
previousValueThe previous value of the field, before changes, only in beforeChange and afterChange hooks.
contextContext passed to this hook. More info can be found under Context
fieldThe field which the hook is running against.
collectionThe collection which the field belongs to. If the field belongs to a global, this will be null.
globalThe global which the field belongs to. If the field belongs to a collection, this will be null.

Return value

All field hooks can optionally modify the return value of the field before the operation continues. Field Hooks may optionally return the value that should be used within the field.

Examples of Field Hooks

To better illustrate how field-level hooks can be applied, here are some specific examples. These demonstrate the flexibility and potential of field hooks in different contexts. Remember, these examples are just a starting point - the true potential of field-level hooks lies in their adaptability to a wide array of use cases.

beforeValidate

Runs before the update operation. This hook allows you to pre-process or format field data before it undergoes validation.

1
import { Field } from 'payload/types'
2
3
const usernameField: Field = {
4
name: 'username',
5
type: 'text',
6
hooks: {
7
beforeValidate: [({ value }) => {
8
// Trim whitespace and convert to lowercase
9
return value.trim().toLowerCase()
10
}],
11
}
12
}

In this example, the beforeValidate hook is used to process the username field. The hook takes the incoming value of the field and transforms it by trimming whitespace and converting it to lowercase. This ensures that the username is stored in a consistent format in the database.

beforeChange

Immediately following validation, beforeChange hooks will run within create and update operations. At this stage, you can be confident that the field data that will be saved to the document is valid in accordance to your field validations.

1
import { Field } from 'payload/types'
2
3
const emailField: Field = {
4
name: 'email',
5
type: 'email',
6
hooks: {
7
beforeChange: [({ value, operation }) => {
8
if (operation === 'create') {
9
// Perform additional validation or transformation for 'create' operation
10
}
11
return value
12
}],
13
}
14
}

In the emailField, the beforeChange hook checks the operation type. If the operation is create, it performs additional validation or transformation on the email field value. This allows for operation-specific logic to be applied to the field.

afterChange

The afterChange hook is executed after a field's value has been changed and saved in the database. This hook is useful for post-processing or triggering side effects based on the new value of the field.

1
import { Field } from 'payload/types'
2
3
const membershipStatusField: Field = {
4
name: 'membershipStatus',
5
type: 'select',
6
options: [
7
{ label: 'Standard', value: 'standard' },
8
{ label: 'Premium', value: 'premium' },
9
{ label: 'VIP', value: 'vip' }
10
],
11
hooks: {
12
afterChange: [({ value, previousValue, req }) => {
13
if (value !== previousValue) {
14
// Log or perform an action when the membership status changes
15
console.log(`User ID ${req.user.id} changed their membership status from ${previousValue} to ${value}.`)
16
// Here, you can implement actions that could track conversions from one tier to another
17
}
18
}],
19
}
20
}

In this example, the afterChange hook is used with a membershipStatusField, which allows users to select their membership level (Standard, Premium, VIP). The hook monitors changes in the membership status. When a change occurs, it logs the update and can be used to trigger further actions, such as tracking conversion from one tier to another or notifying them about changes in their membership benefits.

afterRead

The afterRead hook is invoked after a field value is read from the database. This is ideal for formatting or transforming the field data for output.

1
import { Field } from 'payload/types'
2
3
const dateField: Field = {
4
name: 'createdAt',
5
type: 'date',
6
hooks: {
7
afterRead: [({ value }) => {
8
// Format date for display
9
return new Date(value).toLocaleDateString()
10
}],
11
}
12
}

Here, the afterRead hook for the dateField is used to format the date into a more readable format using toLocaleDateString(). This hook modifies the way the date is presented to the user, making it more user-friendly.

TypeScript

Payload exports a type for field hooks which can be accessed and used as follows:

1
import type { FieldHook } from 'payload/types'
2
3
// Field hook type is a generic that takes three arguments:
4
// 1: The document type
5
// 2: The value type
6
// 3: The sibling data type
7
8
type ExampleFieldHook = FieldHook<ExampleDocumentType, string, SiblingDataType>
9
10
const exampleFieldHook: ExampleFieldHook = (args) => {
11
const {
12
value, // Typed as `string` as shown above
13
data, // Typed as a Partial of your ExampleDocumentType
14
siblingData, // Typed as a Partial of SiblingDataType
15
originalDoc, // Typed as ExampleDocumentType
16
operation,
17
req,
18
} = args
19
20
// Do something here...
21
22
return value // should return a string as typed above, undefined, or null
23
}
Next

Global Hooks