Context

The context object in hooks is used to share data across different hooks. The persists throughout the entire lifecycle of a request and is available within every hook. This allows you to add logic to your hooks based on the request state by setting properties to req.context and using them elsewhere.

When to use Context

Context gives you a way forward on otherwise difficult problems such as:

  1. Passing data between hooks: Needing data in multiple hooks from a 3rd party API, it could be retrieved and used in beforeChange and later used again in an afterChange hook without having to fetch it twice.
  2. Preventing infinite loops: Calling payload.update() on the same document that triggered an afterChange hook will create an infinite loop, control the flow by assigning a no-op condition to context
  3. Passing data to local API: Setting values on the req.context and pass it to payload.create() you can provide additional data to hooks without adding extraneous fields.
  4. Passing data between hooks and middleware or custom endpoints: Hooks could set context across multiple collections and then be used in a final postMiddleware.

How to Use Context

Let's see examples on how context can be used in the first two scenarios mentioned above:

Passing data between hooks

To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it the context in a later hook.

For example:

1
const Customer: CollectionConfig = {
2
slug: 'customers',
3
hooks: {
4
beforeChange: [
5
async ({ context, data }) => {
6
// assign the customerData to context for use later
7
context.customerData = await fetchCustomerData(data.customerID)
8
return {
9
...data,
10
// some data we use here
11
name: context.customerData.name,
12
}
13
},
14
],
15
afterChange: [
16
async ({ context, doc, req }) => {
17
// use context.customerData without needing to fetch it again
18
if (context.customerData.contacted === false) {
19
createTodo('Call Customer', context.customerData)
20
}
21
},
22
],
23
},
24
fields: [
25
/* ... */
26
],
27
}

Preventing infinite loops

Let's say you have an afterChange hook, and you want to do a calculation inside the hook (as the document ID needed for the calculation is available in the afterChange hook, but not in the beforeChange hook). Once that's done, you want to update the document with the result of the calculation.

Bad example:

1
const Customer: CollectionConfig = {
2
slug: 'customers',
3
hooks: {
4
afterChange: [
5
async ({ doc }) => {
6
await payload.update({
7
// DANGER: updating the same slug as the collection in an afterChange will create an infinite loop!
8
collection: 'customers',
9
id: doc.id,
10
data: {
11
...(await fetchCustomerData(data.customerID)),
12
},
13
})
14
},
15
],
16
},
17
fields: [
18
/* ... */
19
],
20
}

Instead of the above, we need to tell the afterChange hook to not run again if it performs the update (and thus not update itself again). We can solve that with context.

Fixed example:

1
const MyCollection: CollectionConfig = {
2
slug: 'slug',
3
hooks: {
4
afterChange: [
5
async ({ context, doc }) => {
6
// return if flag was previously set
7
if (context.triggerAfterChange === false) {
8
return
9
}
10
await payload.update({
11
collection: contextHooksSlug,
12
id: doc.id,
13
data: {
14
...(await fetchCustomerData(data.customerID)),
15
},
16
context: {
17
// set a flag to prevent from running again
18
triggerAfterChange: false,
19
},
20
})
21
},
22
],
23
},
24
fields: [
25
/* ... */
26
],
27
}

Typing context

The default typescript interface for context is { [key: string]: unknown }. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the declare syntax.

This is known as "type augmentation" - a TypeScript feature which allows us to add types to existing objects. Simply put this in any .ts or .d.ts file:

1
import { RequestContext as OriginalRequestContext } from 'payload'
2
3
declare module 'payload' {
4
// Create a new interface that merges your additional fields with the original one
5
export interface RequestContext extends OriginalRequestContext {
6
myObject?: string
7
// ...
8
}
9
}

This will add a the property myObject with a type of string to every context object. Make sure to follow this example correctly, as type augmentation can mess up your types if you do it wrong.

Next

Authentication Overview