v3.60.0

payloadcms/payloadv3.60.0Oct 16, 2025by denolfe

AI Summary

This release adds multiple fallback locales, a settings menu in the admin sidebar, and enhances the multi-tenant plugin with access overrides and support for hasMany on tenant fields.

Key Highlights

  • Multiple fallback locales support
  • Settings menu in admin navigation sidebar
  • Multi-tenant plugin access overrides
  • hasMany support on tenant field

New Features

  • Accept multiple locales in fallbackLocale
  • Add settingsMenu to admin navigation sidebar
  • Allow collection access to be overridden via callback
  • Allow hasMany on tenant field overrides
  • Add collection filtering to UploadFeature
  • Client-side block markdown shortcuts and code blocks

Full Release Notes

## [v3.60.0](https://github.com/payloadcms/payload/compare/v3.59.1...v3.60.0) (2025-10-16)


### ๐Ÿš€ Features

* accept multiple locales in fallbackLocale ([#13822](https://github.com/payloadcms/payload/issues/13822)) ([623a1b8](https://github.com/payloadcms/payload/commit/623a1b8))
* adds settingsMenu to admin navigation sidebar ([#14139](https://github.com/payloadcms/payload/issues/14139)) ([ee8b3cf](https://github.com/payloadcms/payload/commit/ee8b3cf))
* **plugin-multi-tenant:** allow collection access to be overridden via callback ([#14127](https://github.com/payloadcms/payload/issues/14127)) ([c40eec2](https://github.com/payloadcms/payload/commit/c40eec2))
* **plugin-multi-tenant:** allow hasMany on tenant field overrides ([#14120](https://github.com/payloadcms/payload/issues/14120)) ([fb93cd1](https://github.com/payloadcms/payload/commit/fb93cd1))
* **plugin-multi-tenant:** user collection access overrides ([#14119](https://github.com/payloadcms/payload/issues/14119)) ([38b7a60](https://github.com/payloadcms/payload/commit/38b7a60))
* **richtext-lexical:** add collection filtering to UploadFeature, refactor relationship hooks ([#14111](https://github.com/payloadcms/payload/issues/14111)) ([6defba9](https://github.com/payloadcms/payload/commit/6defba9))
* **richtext-lexical:** client-side block markdown shortcuts, code block ([#13813](https://github.com/payloadcms/payload/issues/13813)) ([07a1eff](https://github.com/payloadcms/payload/commit/07a1eff))

**Localization**

- **Multiple fallback locales** - `fallbackLocale` now accepts an array of locales for queries and locale configs. Payload will check each locale in order until finding a value, eliminating the need for manual fallback handling. [#13822](https://github.com/payloadcms/payload/pull/13822)

  ```ts
  /** Local API **/
    await payload.findByID({
      id,
      collection,
      locale: 'en',
      fallbackLocale: ['fr', 'es'],
    })

  /** REST API **/
    await fetch(`${baseURL}/api/${collectionSlug}?locale=en&fallbackLocale[]=fr&fallbackLocale[]=es`)

  /** GraphQL **/
    await restClient.GRAPHQL_POST({
      body,
      query: { locale: 'en', fallbackLocale: ['fr', 'es']},
    })

  /** Locale Configs **/
    locales: [
      {
        code: 'en',
        label: 'English',
        fallbackLocale: ['fr', 'es'],
      },
    ]
  ```

**Admin UI**

- **Settings menu in navigation** - New `admin.components.settingsMenu` config option adds a gear icon above the logout button. Click to open a popup menu with custom admin-level utilities and actions that don't fit into collection or global navigation. [#14139](https://github.com/payloadcms/payload/pull/14139)

  <img width="174" height="239" alt="Screenshot 2025-10-14 at 11 43 37 AM" src="https://github.com/user-attachments/assets/5dca8ea7-ed99-4bc1-89c2-bc6da7d8f323" />

**Multi-Tenant Plugin**

- **Collection access overrides** - New `accessResultOverride` callback allows modifying multi-tenant access control results per operation (read, create, update, delete, readVersions, unlock). Enables custom logic like allowing shared content across tenants. [#14127](https://github.com/payloadcms/payload/pull/14127)

  ```ts
  multiTenantPlugin<ConfigType>({
    collections: {
      posts: {
        accessResultOverride: async ({ accessResult, accessKey, req }) => {
          // here is where you can change the access result or return something entirely different
          if (accessKey === 'read') {
            return {
              or: [
                {
                  isShared: {
                    equals: true,
                  }
                },
                accessResult
              ]
            }
          } else {
            return accessResult
          }
        }
      }
    }
  })
  ```

- **Multiple tenants per document** - Tenant field overrides now support `hasMany` relationships, allowing documents to belong to multiple tenants. [#14120](https://github.com/payloadcms/payload/pull/14120)
- **User collection access overrides** - New `usersAccessResultOverride` callback enables customizing access control on the users collection, overriding default tenant privacy when needed. [#14119](https://github.com/payloadcms/payload/pull/14119)

  ```ts
  usersAccessResultOverride: ({
    accessKey: 'read', // 'create', 'read', 'update', 'delete', 'readVersions', 'unlock'
    accessResult: AccessResult, // the `AccessResult` type
    ...args, // AccessArgs
  }) => {
    // this is where you could adjust what gets returned here.
    if (accessKey === 'read') {
      return true // over simplified example
    }

    // default to returning the result from the plugin
    return accessResult
  }
  ```

**Lexical Rich Text**

- **Upload collection filtering** - `UploadFeature` now supports `disabledCollections` and `enabledCollections` to control which collections appear in the upload drawer. Also refactors enabled relationships logic with cleaner `useEnabledRelationships` hook. [#14111](https://github.com/payloadcms/payload/pull/14111)
- **Client-side markdown shortcuts & code blocks** - Blocks with `admin.jsx` now support markdown shortcuts on the client (previously server-only). Includes pre-made `CodeBlock` component for use in `BlocksFeature`. Also fixes `readOnly` handling across nested fields. [#13813](https://github.com/payloadcms/payload/pull/13813)

  <https://github.com/user-attachments/assets/cfc0d2a8-ffb3-4926-b097-ad9c2b133586>

### ๐Ÿ› Bug Fixes

* `findDistinct` by explicit ID paths in relationships and virtual fields ([#14215](https://github.com/payloadcms/payload/issues/14215)) ([2b1b6ee](https://github.com/payloadcms/payload/commit/2b1b6ee))
* hasMany / polymorphic relationships to custom number IDs ([#14201](https://github.com/payloadcms/payload/issues/14201)) ([a3b3865](https://github.com/payloadcms/payload/commit/a3b3865))
* hide fields with read: false in list view columns, filters, and groupBy ([#14118](https://github.com/payloadcms/payload/issues/14118)) ([bcd40b6](https://github.com/payloadcms/payload/commit/bcd40b6))
* validate Point Field to -180 to 180 for longitude and -90 to 90 for latitude ([#14206](https://github.com/payloadcms/payload/issues/14206)) ([13a1d90](https://github.com/payloadcms/payload/commit/13a1d90))
* urls in upload sizes are not encoded ([#14205](https://github.com/payloadcms/payload/issues/14205)) ([747a049](https://github.com/payloadcms/payload/commit/747a049))
* restoring trashed drafts with empty required fields fails validation ([#14186](https://github.com/payloadcms/payload/issues/14186)) ([3317207](https://github.com/payloadcms/payload/commit/3317207))
* **db-d1-sqlite:** add missing `blocksAsJSON` property ([#14103](https://github.com/payloadcms/payload/issues/14103)) ([f14a38e](https://github.com/payloadcms/payload/commit/f14a38e))
* **db-mongodb:** documents not showing in folders with `useJoinAggregations: false` ([#14155](https://github.com/payloadcms/payload/issues/14155)) ([e613a78](https://github.com/payloadcms/payload/commit/e613a78))
* **db-mongodb:** improve check for `ObjectId` ([#14131](https://github.com/payloadcms/payload/issues/14131)) ([32e2be1](https://github.com/payloadcms/payload/commit/32e2be1))
* **graphql:** bump tsx version to get around esbuild vulnerability ([#14207](https://github.com/payloadcms/payload/issues/14207)) ([d7ec48f](https://github.com/payloadcms/payload/commit/d7ec48f))
* **next:** custom views not overriding built-in single-segment routes ([#14066](https://github.com/payloadcms/payload/issues/14066)) ([691f810](https://github.com/payloadcms/payload/commit/691f810))
* **plugin-multi-tenant:** object reference mutations in addFilterOptionsToFields ([#14150](https://github.com/payloadcms/payload/issues/14150)) ([e62f1fe](https://github.com/payloadcms/payload/commit/e62f1fe))
* **richtext-lexical:** state key collisions when multiple TextStateFeatures are registered ([#14194](https://github.com/payloadcms/payload/issues/14194)) ([f01a6ed](https://github.com/payloadcms/payload/commit/f01a6ed))
* **richtext-lexical:** editor throws an error if OrderedList is registered but not UnorderedList or CheckList ([#14149](https://github.com/payloadcms/payload/issues/14149)) ([1fe75e0](https://github.com/payloadcms/payload/commit/1fe75e0))
* **richtext-lexical:** editing a copied inline block also modifies the original ([#14137](https://github.com/payloadcms/payload/issues/14137)) ([5bacb38](https://github.com/payloadcms/payload/commit/5bacb38))
* **richtext-lexical:** correctly type field property of RenderLexical ([#14141](https://github.com/payloadcms/payload/issues/14141)) ([cd94f8e](https://github.com/payloadcms/payload/commit/cd94f8e))
* **richtext-lexical:** improve type autocomplete and assignability for lexical nodes ([#14112](https://github.com/payloadcms/payload/issues/14112)) ([a46faf1](https://github.com/payloadcms/payload/commit/a46faf1))
* **sdk:** `pagination` is not passed to search params ([#14126](https://github.com/payloadcms/payload/issues/14126)) ([ee9f160](https://github.com/payloadcms/payload/commit/ee9f160))
* **storage-gcs:** bump @google-cloud/storage for vulnerability ([#14199](https://github.com/payloadcms/payload/issues/14199)) ([1077aa7](https://github.com/payloadcms/payload/commit/1077aa7))
* **storage-r2:** uploads with prefix don't work, add `test/storage-r2` ([#14132](https://github.com/payloadcms/payload/issues/14132)) ([4fd4cb0](https://github.com/payloadcms/payload/commit/4fd4cb0))
* **templates:** encoding and decoding slugs in website template ([#14216](https://github.com/payloadcms/payload/issues/14216)) ([cacf523](https://github.com/payloadcms/payload/commit/cacf523))
* **templates:** ecommerce seeding issue ([#14196](https://github.com/payloadcms/payload/issues/14196)) ([d65b8c6](https://github.com/payloadcms/payload/commit/d65b8c6))

### ๐Ÿ›  Refactors

* **richtext-lexical:** add jsdoc to deprecated HTMLConverterFeature ([#14193](https://github.com/payloadcms/payload/issues/14193)) ([0b718bd](https://github.com/payloadcms/payload/commit/0b718bd))

### ๐Ÿ“š Documentation

* revert sentry docs change ([#14113](https://github.com/payloadcms/payload/issues/14113)) ([8844eaf](https://github.com/payloadcms/payload/commit/8844eaf))
* incorrect pg driver injection example in Sentry integration docs ([#14088](https://github.com/payloadcms/payload/issues/14088)) ([95dbaed](https://github.com/payloadcms/payload/commit/95dbaed))

### ๐Ÿงช Tests

* allows running Jest tests through Test Explorer in VSCode and other improvements ([#13751](https://github.com/payloadcms/payload/issues/13751)) ([0d92a43](https://github.com/payloadcms/payload/commit/0d92a43))

### โš™๏ธ CI

* notify website repo on release [skip ci] ([#14140](https://github.com/payloadcms/payload/issues/14140)) ([a36e5d2](https://github.com/payloadcms/payload/commit/a36e5d2))
* add website dispatch event, testing for releases [skip ci] ([#14138](https://github.com/payloadcms/payload/issues/14138)) ([b0d31ba](https://github.com/payloadcms/payload/commit/b0d31ba))
* add missing labels to bug report template [skip ci] ([#14134](https://github.com/payloadcms/payload/issues/14134)) ([3069e5b](https://github.com/payloadcms/payload/commit/3069e5b))
* update generate-template-variations script to cover cloudflare template ([#14105](https://github.com/payloadcms/payload/issues/14105)) ([e78057b](https://github.com/payloadcms/payload/commit/e78057b))

### ๐Ÿก Chores

* better audit dependencies script ([#14189](https://github.com/payloadcms/payload/issues/14189)) ([f9e75a4](https://github.com/payloadcms/payload/commit/f9e75a4))
* ignore agent local files [skip ci] ([#14184](https://github.com/payloadcms/payload/issues/14184)) ([7ecb5a0](https://github.com/payloadcms/payload/commit/7ecb5a0))
* **drizzle:** bump drizzle-orm and drizzle-kit for vulnerabilities ([#14200](https://github.com/payloadcms/payload/issues/14200)) ([4afa286](https://github.com/payloadcms/payload/commit/4afa286))
* **email-nodemailer:** bump nodemailer dependencies to latest ([#14121](https://github.com/payloadcms/payload/issues/14121)) ([077c6f5](https://github.com/payloadcms/payload/commit/077c6f5))

### ๐Ÿค Contributors

- Sasha (@r1tsuu)
- Patrik (@PatrikKozak)
- Paul (@paulpopus)
- Jessica Rynkar (@jessrynkar)
- German Jablonski (@GermanJablo)
- Elliot DeNolf (@denolfe)
- Elliott W (@elliott-w)
- Jarrod Flesch (@JarrodMFlesch)
- Alessio Gravili (@AlessioGr)
- Diogo Gaspar (@shadowoff09)