Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions docs/hooks/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ The following arguments are provided to the `afterError` Hook:
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. This will be `undefined` if the hook is executed from a non-collection endpoint or GraphQL. |
| **`result`** | The formatted error result object, available if the hook is executed from a REST context. |

## Async vs. Synchronous
## Awaited vs. non-blocking hooks

All Hooks can be written as either synchronous or asynchronous functions. Choosing the right type depends on your use case, but switching between the two is as simple as adding or removing the `async` keyword.
Hooks can either block the request until they finish or run without blocking it. What matters is whether your hook returns a Promise.

#### Asynchronous
Awaited (blocking): If your hook returns a Promise (for example, if it’s declared async), Payload will wait for it to resolve before continuing that lifecycle step. Use this when your hook needs to modify data or influence the response. Hooks that return Promises run in series at the same lifecycle stage.

If the Hook should modify data before a Document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function. This way you can be sure that your Hook completes before the operation's lifecycle continues.
Non-blocking (sometimes called “fire-and-forget”): If your hook does not return a Promise (returns nothing), Payload will not wait for it to finish. This can be useful for side-effects that don’t affect the outcome of the operation, but keep in mind that any work started this way might continue after the request has already completed.

Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts.
**Declaring a function with async does not make it “synchronous.” The async keyword simply makes the function return a Promise automatically — which is why Payload then awaits it.**

<Banner type="success">
**Tip:** If your hook executes a long-running task that doesn't affect the
Expand All @@ -104,9 +104,24 @@ Async hooks are run in series - so if you have two async hooks defined, the seco
continue processing without waiting for the task to complete.
</Banner>

#### Synchronous
**Awaited**

If your Hook simply performs a side-effect, such as mutating document data, it might be okay to define it synchronously, so the Payload operation does not have to wait for your hook to complete.
```ts
const beforeChange = async ({ data }) => {
const enriched = await fetchProfile(data.userId) // Payload waits here
return { ...data, profile: enriched }
}
```

**Non-blocking**

```ts
const afterChange = ({ doc }) => {
// Trigger side-effect without blocking
pingAnalyticsService(doc.id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the void keyword to signalize that this is a floating promise (unawaited async function call). By default, typescript-eslint will throw a warning if this is not set:

void pingAnalyticsService(doc.id)

// No return → Payload does not wait
}
```

## Server-only Execution

Expand Down