v3.74.0

payloadcms/payloadv3.74.0Jan 30, 2026by denolfe

AI Summary

Major feature release with access override in hooks, strictDraftTypes enforcement, custom UnpublishButton, R2 multipart uploads, and UI improvements.

Key Highlights

  • Access override in document-level hooks - access overrideAccess value in beforeChange hooks
  • strictDraftTypes now enforced across all Local API operations (not just queries)
  • Custom UnpublishButton component support
  • R2 multipart client uploads to avoid Worker memory limits
  • Popup component improvements - prevent close attribute and portal className

New Features

  • Thread override access in doc level hooks
  • Extend strictDraftTypes to all draft operations
  • Custom UnpublishButton component
  • R2 multipart client uploads
  • Popup prevent close attribute (data-popup-prevent-close)
  • Popup portal className customization

Full Release Notes

## [v3.74.0](https://github.com/payloadcms/payload/compare/v3.73.0...v3.74.0) (2026-01-30)


### ๐Ÿš€ Features

* thread override access in doc level hooks ([#15421](https://github.com/payloadcms/payload/issues/15421)) ([85d5263](https://github.com/payloadcms/payload/commit/85d5263))
* extend strictDraftTypes to all draft operations ([#15292](https://github.com/payloadcms/payload/issues/15292)) ([9239164](https://github.com/payloadcms/payload/commit/9239164))
* add support for custom UnpublishButton component ([#15400](https://github.com/payloadcms/payload/issues/15400)) ([94254da](https://github.com/payloadcms/payload/commit/94254da))
* **storage-r2:** client uploads using R2 multipart api ([#14733](https://github.com/payloadcms/payload/issues/14733)) ([5c38902](https://github.com/payloadcms/payload/commit/5c38902))
* **ui:** allows opting out of popup closing logic ([#15407](https://github.com/payloadcms/payload/issues/15407)) ([fb2b602](https://github.com/payloadcms/payload/commit/fb2b602))
* **ui:** allows customizing Popup component portal className ([#15406](https://github.com/payloadcms/payload/issues/15406)) ([0f55464](https://github.com/payloadcms/payload/commit/0f55464))

---

**Override Access in Document-Level Hooks** - Access the `overrideAccess` value inside collection and global hooks. Useful when hook logic needs to know whether access control was bypassed, such as when querying related documents up a hierarchy. [#15421](https://github.com/payloadcms/payload/pull/15421)

```ts
export const Posts: CollectionConfig = {
  slug: 'posts',
  hooks: {
    beforeChange: [
      ({ overrideAccess, req }) => {
        if (overrideAccess) {
          // Access control was bypassed
        }
      },
    ],
  },
}
```

---

**Extended strictDraftTypes to All Operations** - When `strictDraftTypes: true` is enabled, TypeScript now enforces draft type safety across **all Local API operations** (not just queries). The `draft` option is forbidden for collections/globals without drafts enabled, preventing silent runtime behavior where draft flags are ignored. [#15292](https://github.com/payloadcms/payload/pull/15292)

```ts
import { buildConfig } from 'payload'

export default buildConfig({
  typescript: {
    strictDraftTypes: true, // Enables compile-time draft enforcement
  },
  // ...
})
```

โš ๏ธ **Note:** Generic collection slugs may require explicit type assertions when using draft options.

---

**Custom UnpublishButton Component** - Customize the `UnpublishButton` in collection and global configs, following the same pattern as `PublishButton` and `SaveButton`. Previously hardcoded. [#15400](https://github.com/payloadcms/payload/pull/15400)

```ts
export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    components: {
      edit: {
        UnpublishButton: '/components/CustomUnpublishButton',
      },
    },
  },
}
```

---

**R2 Multipart Client Uploads (storage-r2)** - Upload large files directly from the client using R2's multipart API. Files are split into smaller parts and uploaded separately, avoiding Cloudflare Worker memory limits. [#14733](https://github.com/payloadcms/payload/pull/14733)

---

**Popup Prevent Close Attribute (ui)** - Add interactive elements inside popups without triggering close behavior by adding the `data-popup-prevent-close` attribute. [#15407](https://github.com/payloadcms/payload/pull/15407)

```tsx
<Popup>
  <button data-popup-prevent-close onClick={handleClick}>
    Click me without closing
  </button>
</Popup>
```

---

**Popup Portal className (ui)** - Customize the Popup component's portal container with the new `portalClassName` prop. [#15406](https://github.com/payloadcms/payload/pull/15406)

```tsx
<Popup portalClassName="my-custom-portal-class">
  {/* content */}
</Popup>
```

### ๐Ÿ› Bug Fixes

* isolate payload-preferences by auth collection ([#15425](https://github.com/payloadcms/payload/issues/15425)) ([2dc2e7c](https://github.com/payloadcms/payload/commit/2dc2e7c))
* traverseFields returning wrong parentPath dot notation for non-localised tabs ([#15394](https://github.com/payloadcms/payload/issues/15394)) ([99b051e](https://github.com/payloadcms/payload/commit/99b051e))
* widgets and other features failing with transitive dependency imports ([#15392](https://github.com/payloadcms/payload/issues/15392)) ([5561799](https://github.com/payloadcms/payload/commit/5561799))
* replace deprecated scmp with crypto.timingSafeEqual ([#15322](https://github.com/payloadcms/payload/issues/15322)) ([2511c02](https://github.com/payloadcms/payload/commit/2511c02))
* find afterRead hooks should behave like findByID ([#15357](https://github.com/payloadcms/payload/issues/15357)) ([3e27155](https://github.com/payloadcms/payload/commit/3e27155))
* remove depth from count operation types ([#15356](https://github.com/payloadcms/payload/issues/15356)) ([dfc1600](https://github.com/payloadcms/payload/commit/dfc1600))
* publish button incorrectly shown after saving draft when access denied ([#15319](https://github.com/payloadcms/payload/issues/15319)) ([e833fe6](https://github.com/payloadcms/payload/commit/e833fe6))
* **next:** version view throws useLocale() server error ([#15380](https://github.com/payloadcms/payload/issues/15380)) ([2ce26fa](https://github.com/payloadcms/payload/commit/2ce26fa))
* **next:** ensure query preset from url is applied ([#15323](https://github.com/payloadcms/payload/issues/15323)) ([592f404](https://github.com/payloadcms/payload/commit/592f404))
* **next:** ensure save preset button is not shown when there are no changes ([#15320](https://github.com/payloadcms/payload/issues/15320)) ([e9af097](https://github.com/payloadcms/payload/commit/e9af097))
* **plugin-cloud-storage:** prevent infinite loop when cropping media ([#15393](https://github.com/payloadcms/payload/issues/15393)) ([345a9c7](https://github.com/payloadcms/payload/commit/345a9c7))
* **plugin-cloud-storage:** adds beforeChange hook to generate url on create ([#15401](https://github.com/payloadcms/payload/issues/15401)) ([d269d39](https://github.com/payloadcms/payload/commit/d269d39))
* **richtext-slate:** localized indicator not displaying in label ([#15412](https://github.com/payloadcms/payload/issues/15412)) ([126f713](https://github.com/payloadcms/payload/commit/126f713))
* **ui:** use the formatAdminUrl function to generate unpublish url ([#15375](https://github.com/payloadcms/payload/issues/15375)) ([453e8a6](https://github.com/payloadcms/payload/commit/453e8a6))
* **ui:** restore default columns after clearing query preset ([#15360](https://github.com/payloadcms/payload/issues/15360)) ([029699d](https://github.com/payloadcms/payload/commit/029699d))
* **ui:** prevent globals crash with arrays fields when lock state user is undefined in handleDocumentLocking ([#15259](https://github.com/payloadcms/payload/issues/15259)) ([ea76ca0](https://github.com/payloadcms/payload/commit/ea76ca0))
* **ui:** getEntityConfig did not respect globalSlug if collectionSlug is undefined ([#15362](https://github.com/payloadcms/payload/issues/15362)) ([b54059c](https://github.com/payloadcms/payload/commit/b54059c))

### ๐Ÿ“š Documentation

* clarify beforeValidate and beforeChange hook data behavior ([#15300](https://github.com/payloadcms/payload/issues/15300)) ([ba9605e](https://github.com/payloadcms/payload/commit/ba9605e))
* add payload.logger.error usage guidelines to CLAUDE.md ([#15398](https://github.com/payloadcms/payload/issues/15398)) ([cdbfda2](https://github.com/payloadcms/payload/commit/cdbfda2))
* updated redirects plugin integration docs and postgres connection troubleshooting ([#15383](https://github.com/payloadcms/payload/issues/15383)) ([9b2221e](https://github.com/payloadcms/payload/commit/9b2221e))
* improve select jsdocs and empty select docs ([#15336](https://github.com/payloadcms/payload/issues/15336)) ([4181a12](https://github.com/payloadcms/payload/commit/4181a12))

### ๐Ÿงช Tests

* make integration tests for the `fields` and `select` suites faster ([#15434](https://github.com/payloadcms/payload/issues/15434)) ([26ba779](https://github.com/payloadcms/payload/commit/26ba779))

### ๐Ÿ“ Templates

* **deps:** bump Next.js versions ([#15365](https://github.com/payloadcms/payload/issues/15365)) ([5658ec2](https://github.com/payloadcms/payload/commit/5658ec2))

### ๐Ÿ”จ Build

* add node-gyp for native module rebuilds ([#15396](https://github.com/payloadcms/payload/issues/15396)) ([50c087f](https://github.com/payloadcms/payload/commit/50c087f))

### โš™๏ธ CI

* enable retries for int and unit tests ([#15397](https://github.com/payloadcms/payload/issues/15397)) ([8b21263](https://github.com/payloadcms/payload/commit/8b21263))
* cache e2e prod preparation to run once instead of per-shard, various improvements and flake fixes ([#15368](https://github.com/payloadcms/payload/issues/15368)) ([2b3b9d5](https://github.com/payloadcms/payload/commit/2b3b9d5))
* automatically shard int tests ([#15367](https://github.com/payloadcms/payload/issues/15367)) ([1041b15](https://github.com/payloadcms/payload/commit/1041b15))
* disable playwright tracing for first test runs, to improve performance and reduce flakes ([#15337](https://github.com/payloadcms/payload/issues/15337)) ([ab603c3](https://github.com/payloadcms/payload/commit/ab603c3))
* automatically shard e2e tests ([#15366](https://github.com/payloadcms/payload/issues/15366)) ([2f19f8f](https://github.com/payloadcms/payload/commit/2f19f8f))
* add missing value property to outputs ([#15338](https://github.com/payloadcms/payload/issues/15338)) ([479b057](https://github.com/payloadcms/payload/commit/479b057))
* bump monorepo Node.js version from 23.11.0 to 24.13.0 ([#15364](https://github.com/payloadcms/payload/issues/15364)) ([d95c365](https://github.com/payloadcms/payload/commit/d95c365))
* add linked PR feature to popular-issues action ([#15355](https://github.com/payloadcms/payload/issues/15355)) ([614f13c](https://github.com/payloadcms/payload/commit/614f13c))

### ๐Ÿก Chores

* export getSafeFileName utility ([#15424](https://github.com/payloadcms/payload/issues/15424)) ([6b45fee](https://github.com/payloadcms/payload/commit/6b45fee))
* replace rmdirSync to rmSync ([#15420](https://github.com/payloadcms/payload/issues/15420)) ([17c0a3f](https://github.com/payloadcms/payload/commit/17c0a3f))
* **claude:** add respond-to-discussion-features claude command ([#15391](https://github.com/payloadcms/payload/issues/15391)) ([1f7fe63](https://github.com/payloadcms/payload/commit/1f7fe63))
* **deps:** bump form-data, tar, wrangler, and @opennextjs/cloudflare dependencies ([#15435](https://github.com/payloadcms/payload/issues/15435)) ([5875cd0](https://github.com/payloadcms/payload/commit/5875cd0))
* **deps:** bump tsx ([#15363](https://github.com/payloadcms/payload/issues/15363)) ([7043e3f](https://github.com/payloadcms/payload/commit/7043e3f))
* **plugin-multi-tenant:** removes basePath arg ([#15202](https://github.com/payloadcms/payload/issues/15202)) ([bd80784](https://github.com/payloadcms/payload/commit/bd80784))
* **ui:** move unpublish actions into document controls dropdown ([#15417](https://github.com/payloadcms/payload/issues/15417)) ([99d61db](https://github.com/payloadcms/payload/commit/99d61db))

### ๐Ÿค Contributors

- Ricardo Tavares (@rjgtav)
- Patrik (@PatrikKozak)
- German Jablonski (@GermanJablo)
- Jessica Rynkar (@jessrynkar)
- Jarrod Flesch (@JarrodMFlesch)
- Tobias Odendahl (@tak-amboss)
- Paul (@paulpopus)
- Elliot DeNolf (@denolfe)
- Sean Zubrickas (@zubricks)
- Alessio Gravili (@AlessioGr)
- Georg Schelkshorn (@GeorgS)
- Muhammad Hassaan Farooq (@Muhammad-Hassaan-Farooq)
- RajeshKumar11 (@RajeshKumar11)