Skip to content

Commit 3af546e

Browse files
authored
feat: global beforeOperation hook (#13768)
Adds support for the `beforeOperation` hook on globals. Runs before all other hooks to either modify the arguments that operations receive, or perform side-effects before an operation begins. ```ts import type { GlobalConfig } from 'payload' const MyGlobal: GlobalConfig = { // ... hooks: { beforeOperation: [] } } ``` --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211317005907890
1 parent 4482eaf commit 3af546e

File tree

11 files changed

+143
-18
lines changed

11 files changed

+143
-18
lines changed

docs/hooks/collections.mdx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ export const CollectionWithHooks: CollectionConfig = {
6767

6868
The `beforeOperation` hook can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
6969

70-
Available Collection operations include `create`, `read`, `update`, `delete`, `login`, `refresh`, and `forgotPassword`.
71-
7270
```ts
7371
import type { CollectionBeforeOperationHook } from 'payload'
7472

@@ -83,12 +81,12 @@ const beforeOperationHook: CollectionBeforeOperationHook = async ({
8381

8482
The following arguments are provided to the `beforeOperation` hook:
8583

86-
| Option | Description |
87-
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
88-
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
89-
| **`context`** | Custom context passed between Hooks. [More details](./context). |
90-
| **`operation`** | The name of the operation that this hook is running within. |
91-
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
84+
| Option | Description |
85+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86+
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. Available options include: `autosave`, `count`, `countVersions`, `create`, `delete`, `forgotPassword`, `login`, `read`, `readDistinct`, `refresh`, `resetPassword`, `restoreVersion`, and `update`. |
87+
| **`context`** | Custom context passed between Hooks. [More details](./context). |
88+
| **`operation`** | The name of the operation that this hook is running within. |
89+
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
9290

9391
### beforeValidate
9492

docs/hooks/globals.mdx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const GlobalWithHooks: GlobalConfig = {
3838
// ...
3939
// highlight-start
4040
hooks: {
41+
beforeOperation: [(args) => {...}],
4142
beforeValidate: [(args) => {...}],
4243
beforeChange: [(args) => {...}],
4344
beforeRead: [(args) => {...}],
@@ -48,6 +49,31 @@ const GlobalWithHooks: GlobalConfig = {
4849
}
4950
```
5051

52+
### beforeOperation
53+
54+
The `beforeOperation` hook can be used to modify the arguments that operations accept or execute side-effects that run before an operation begins.
55+
56+
```ts
57+
import type { GlobalBeforeOperationHook } from 'payload'
58+
59+
const beforeOperationHook: GlobalBeforeOperationHook = async ({
60+
args,
61+
operation,
62+
req,
63+
}) => {
64+
return args // return modified operation arguments as necessary
65+
}
66+
```
67+
68+
The following arguments are provided to the `beforeOperation` hook:
69+
70+
| Option | Description |
71+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
72+
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. Available operation include: `countVersions`, `read`, `restoreVersion`, and `update`. |
73+
| **`context`** | Custom context passed between Hooks. [More details](./context). |
74+
| **`operation`** | The name of the operation that this hook is running within. |
75+
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
76+
5177
### beforeValidate
5278

5379
Runs during the `update` operation. This hook allows you to add or format data before the incoming data is validated server-side.

packages/payload/src/collections/config/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ type CreateOrUpdateOperation = Extract<HookOperationType, 'create' | 'update'>
9292

9393
export type BeforeOperationHook = (args: {
9494
args?: any
95-
/** The collection which this hook is being run on */
95+
/**
96+
* The collection which this hook is being run on
97+
*/
9698
collection: SanitizedCollectionConfig
9799
context: RequestContext
98100
/**

packages/payload/src/globals/config/sanitize.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const sanitizeGlobal = async (
2424
if (global._sanitized) {
2525
return global as SanitizedGlobalConfig
2626
}
27+
2728
global._sanitized = true
2829

2930
global.label = global.label || toWords(global.slug)
@@ -33,35 +34,43 @@ export const sanitizeGlobal = async (
3334
// /////////////////////////////////
3435

3536
global.endpoints = global.endpoints ?? []
37+
3638
if (!global.hooks) {
3739
global.hooks = {}
3840
}
41+
3942
if (!global.access) {
4043
global.access = {}
4144
}
45+
4246
if (!global.admin) {
4347
global.admin = {}
4448
}
4549

4650
if (!global.access.read) {
4751
global.access.read = defaultAccess
4852
}
53+
4954
if (!global.access.update) {
5055
global.access.update = defaultAccess
5156
}
5257

5358
if (!global.hooks.beforeValidate) {
5459
global.hooks.beforeValidate = []
5560
}
61+
5662
if (!global.hooks.beforeChange) {
5763
global.hooks.beforeChange = []
5864
}
65+
5966
if (!global.hooks.afterChange) {
6067
global.hooks.afterChange = []
6168
}
69+
6270
if (!global.hooks.beforeRead) {
6371
global.hooks.beforeRead = []
6472
}
73+
6574
if (!global.hooks.afterRead) {
6675
global.hooks.afterRead = []
6776
}

packages/payload/src/globals/config/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ export type AfterReadHook = (args: {
7777
req: PayloadRequest
7878
}) => any
7979

80+
export type HookOperationType = 'countVersions' | 'read' | 'restoreVersion' | 'update'
81+
82+
export type BeforeOperationHook = (args: {
83+
args?: any
84+
context: RequestContext
85+
/**
86+
* The Global which this hook is being run on
87+
* */
88+
global: SanitizedGlobalConfig
89+
/**
90+
* Hook operation being performed
91+
*/
92+
operation: HookOperationType
93+
req: PayloadRequest
94+
}) => any
95+
8096
export type GlobalAdminOptions = {
8197
/**
8298
* Custom admin components
@@ -187,6 +203,7 @@ export type GlobalConfig<TSlug extends GlobalSlug = any> = {
187203
afterChange?: AfterChangeHook[]
188204
afterRead?: AfterReadHook[]
189205
beforeChange?: BeforeChangeHook[]
206+
beforeOperation?: BeforeOperationHook[]
190207
beforeRead?: BeforeReadHook[]
191208
beforeValidate?: BeforeValidateHook[]
192209
}

packages/payload/src/globals/operations/countGlobalVersions.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@ export const countGlobalVersionsOperation = async <TSlug extends GlobalSlug>(
2828
const req = args.req!
2929
const { payload } = req
3030

31+
// /////////////////////////////////////
32+
// beforeOperation - Global
33+
// /////////////////////////////////////
34+
35+
if (global.hooks?.beforeOperation?.length) {
36+
for (const hook of global.hooks.beforeOperation) {
37+
args =
38+
(await hook({
39+
args,
40+
context: req.context,
41+
global,
42+
operation: 'countVersions',
43+
req,
44+
})) || args
45+
}
46+
}
47+
3148
// /////////////////////////////////////
3249
// Access
3350
// /////////////////////////////////////

packages/payload/src/globals/operations/findOne.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
5252
} = args
5353

5454
try {
55+
// /////////////////////////////////////
56+
// beforeOperation - Global
57+
// /////////////////////////////////////
58+
59+
if (globalConfig.hooks?.beforeOperation?.length) {
60+
for (const hook of globalConfig.hooks.beforeOperation) {
61+
args =
62+
(await hook({
63+
args,
64+
context: args.req.context,
65+
global: globalConfig,
66+
operation: 'read',
67+
req: args.req,
68+
})) || args
69+
}
70+
}
71+
5572
// /////////////////////////////////////
5673
// Retrieve and execute access
5774
// /////////////////////////////////////

packages/payload/src/globals/operations/restoreVersion.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ export const restoreVersionOperation = async <T extends TypeWithVersion<T> = any
3131
try {
3232
const shouldCommit = await initTransaction(req)
3333

34+
// /////////////////////////////////////
35+
// beforeOperation - Global
36+
// /////////////////////////////////////
37+
38+
if (globalConfig.hooks?.beforeOperation?.length) {
39+
for (const hook of globalConfig.hooks.beforeOperation) {
40+
args =
41+
(await hook({
42+
args,
43+
context: req.context,
44+
global: globalConfig,
45+
operation: 'restoreVersion',
46+
req,
47+
})) || args
48+
}
49+
}
50+
3451
// /////////////////////////////////////
3552
// Access
3653
// /////////////////////////////////////

packages/payload/src/globals/operations/update.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ export const updateOperation = async <
7777
try {
7878
const shouldCommit = !disableTransaction && (await initTransaction(req))
7979

80+
// /////////////////////////////////////
81+
// beforeOperation - Global
82+
// /////////////////////////////////////
83+
84+
if (globalConfig.hooks?.beforeOperation?.length) {
85+
for (const hook of globalConfig.hooks.beforeOperation) {
86+
args =
87+
(await hook({
88+
args,
89+
context: args.req.context,
90+
global: globalConfig,
91+
operation: 'update',
92+
req: args.req,
93+
})) || args
94+
}
95+
}
96+
8097
let { data } = args
8198

8299
const shouldSaveDraft = Boolean(draftArg && globalConfig.versions?.drafts)

packages/payload/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,7 @@ export type {
15751575
AfterChangeHook as GlobalAfterChangeHook,
15761576
AfterReadHook as GlobalAfterReadHook,
15771577
BeforeChangeHook as GlobalBeforeChangeHook,
1578+
BeforeOperationHook as GlobalBeforeOperationHook,
15781579
BeforeReadHook as GlobalBeforeReadHook,
15791580
BeforeValidateHook as GlobalBeforeValidateHook,
15801581
DataFromGlobalSlug,

0 commit comments

Comments
 (0)