---
title: "Collection Hooks"
aliases: [collection-hooks, payload-collection-hooks]
tags: [payloadcms, hooks, collections, lifecycle, auth]
sources: [raw/hooks__collections.md]
created: 2026-05-15
updated: 2026-05-15
---

Collection Hooks run on Documents within a specific [[wiki/payloadcms/collection-config|Collection]], letting you execute custom logic at precise moments in the Document lifecycle.

```ts
export const MyCollection: CollectionConfig = {
  hooks: {
    beforeOperation: [fn],
    beforeValidate: [fn],
    beforeChange: [fn],
    afterChange: [fn],
    beforeRead: [fn],
    afterRead: [fn],
    beforeDelete: [fn],
    afterDelete: [fn],
    afterOperation: [fn],
    afterError: [fn],
    // Auth-only:
    beforeLogin: [fn], afterLogin: [fn], afterLogout: [fn],
    afterRefresh: [fn], afterMe: [fn], afterForgotPassword: [fn],
    refresh: [fn], me: [fn],
  },
}
```

Each hook accepts an **array** of synchronous or asynchronous functions.

---

## Hook Reference

### Write Lifecycle (create / update)

| Hook | When | Typical use |
|------|------|-------------|
| `beforeOperation` | Before any operation starts | Modify operation args, early side-effects |
| `beforeValidate` | Before server validation | Format / enrich incoming data |
| `beforeChange` | After validation, before DB write | Data transformation, enforce business rules |
| `afterChange` | After DB write | Sync to CRM, recalculate aggregates |

**Order:** `beforeOperation` → `beforeValidate` → client `validate` → server `validate` → `beforeChange` → DB write → `afterChange` → `afterOperation`

### Read Lifecycle

| Hook | When | Notes |
|------|------|-------|
| `beforeRead` | Before locale-flattening / hidden-field removal | Has access to **all locales** and hidden fields |
| `afterRead` | Last step before response | Locales flattened, protected fields removed |

### Delete Lifecycle

| Hook | Return value |
|------|-------------|
| `beforeDelete` | Discarded |
| `afterDelete` | Discarded |

### Error Hook

`afterError` — fires on any Payload application error. Use for Sentry/DataDog/email alerts. Can transform the result object and HTTP status code.

---

## Key Arguments

### `data` vs `originalDoc`

> **`data` is a delta** — it only contains fields being changed, NOT the full document.

```ts
// On UPDATE — get id from originalDoc, not data
const id = operation === 'update' ? originalDoc.id : undefined

// On CREATE — id is not available until afterChange
```

| Hook | `data` | `originalDoc` | `doc` |
|------|--------|--------------|-------|
| `beforeValidate` | changed fields | full pre-change doc (update only) | — |
| `beforeChange` | changed fields | full pre-change doc (update only) | — |
| `afterChange` | changed fields | — | full resulting doc |
| `afterRead` | — | — | full resulting doc |

### Common Arguments (all hooks)

- **`collection`** — Collection config object
- **`context`** — custom pass-through between hooks (see [[wiki/payloadcms/hooks|Hooks overview]])
- **`req`** — Web Request (mocked for [[wiki/payloadcms/local-api|Local API]])
- **`operation`** — `'create' | 'update' | 'delete' | 'find' | ...`

---

## Auth-Enabled Hooks

For collections with `auth: true`. See [[wiki/payloadcms/authentication-overview|Authentication Overview]].

| Hook | Fires | Can modify |
|------|-------|-----------|
| `beforeLogin` | After credentials matched, before token | Return modified user or throw to deny login |
| `afterLogin` | After successful login | Return modified user |
| `afterLogout` | After logout | Side-effects only |
| `afterRefresh` | After token refresh | Side-effects only |
| `afterMe` | After `/me` request | Return modified response |
| `afterForgotPassword` | After forgot-password | Return value discarded |
| `refresh` | Replaces default refresh logic | Return value skips default operation |
| `me` | Replaces default me logic | Return value skips default operation |

---

## TypeScript

```ts
import type {
  CollectionBeforeOperationHook,
  CollectionBeforeValidateHook,
  CollectionBeforeChangeHook,
  CollectionAfterChangeHook,
  CollectionAfterReadHook,
  CollectionBeforeReadHook,
  CollectionBeforeDeleteHook,
  CollectionAfterDeleteHook,
  CollectionBeforeLoginHook,
  CollectionAfterLoginHook,
  CollectionAfterLogoutHook,
  CollectionAfterRefreshHook,
  CollectionAfterMeHook,
  CollectionAfterForgotPasswordHook,
  CollectionRefreshHook,
  CollectionMeHook,
} from 'payload'

// Generic typing for doc/data
const afterChangeHook: CollectionAfterChangeHook<Post> = async ({ doc, previousDoc }) => {
  return doc
}
```

---

## Key Takeaways

- **`data` is always a partial delta** — never assume `id` or unchanged fields are present; use `originalDoc` on updates, `afterChange` for the `id` on creates
- **`beforeChange` data is unvalidated** — treat it as raw user input; required fields may be missing
- **`beforeRead`** sees all locales and hidden fields before Payload strips them — use it for internal transforms
- **`afterRead`** sees the cleaned, user-visible document — use it for computed props exposed to clients
- Auth hooks (`beforeLogin`, `refresh`, `me`) can **replace default behavior** by returning early — powerful but be careful
- `afterError` hook is the right place for Sentry / alerting integration, not `try/catch` in every hook
- Field-level hooks are an alternative for isolating logic — see [[wiki/payloadcms/hooks|Hooks Overview]]

---

## Sources

- `raw/hooks__collections.md` (source: https://payloadcms.com/docs/hooks/collections)
