docs/hooks/context.mdx
The context object is used to share data across different Hooks. This persists throughout the entire lifecycle of a request and is available within every Hook. By setting properties to req.context, you can effectively share logic across multiple Hooks.
Context gives you a way forward on otherwise difficult problems such as:
beforeChange and later used again in an afterChange hook without having to fetch it twice.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 contextreq.context and pass it to payload.create() you can provide additional data to hooks without adding extraneous fields.postMiddleware.Let's see examples on how context can be used in the first two scenarios mentioned above:
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it in the context of a later hook.
For example:
import type { CollectionConfig } from 'payload'
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
beforeChange: [
async ({ context, data }) => {
// assign the customerData to context for use later
context.customerData = await fetchCustomerData(data.customerID)
return {
...data,
// some data we use here
name: context.customerData.name,
}
},
],
afterChange: [
async ({ context, doc, req }) => {
// use context.customerData without needing to fetch it again
if (context.customerData.contacted === false) {
createTodo('Call Customer', context.customerData)
}
},
],
},
fields: [
/* ... */
],
}
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:
import type { CollectionConfig } from 'payload'
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
afterChange: [
async ({ doc, req }) => {
await req.payload.update({
// DANGER: updating the same slug as the collection in an afterChange will create an infinite loop!
collection: 'customers',
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID)),
},
})
},
],
},
fields: [
/* ... */
],
}
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:
import type { CollectionConfig } from 'payload'
const MyCollection: CollectionConfig = {
slug: 'slug',
hooks: {
afterChange: [
async ({ context, doc, req }) => {
// return if flag was previously set
if (context.triggerAfterChange === false) {
return
}
await req.payload.update({
collection: contextHooksSlug,
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID)),
},
context: {
// set a flag to prevent from running again
triggerAfterChange: false,
},
})
},
],
},
fields: [
/* ... */
],
}
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 module syntax.
This is known as module augmentation / declaration merging, a TypeScript feature which allows us to add properties to existing types. Simply put this in any .ts or .d.ts file:
declare module 'payload' {
// Augment the RequestContext interface to include your custom properties
export interface RequestContext {
myObject?: string
// ...
}
}
This will add the property myObject with a type of string to every context object. Make sure to follow this example correctly, as module augmentation can mess up your types if you do it wrong.