v3.90.0

bespokelabsai/nimblev3.90.0Sep 18, 2026by denolfe

Upgrading?

AI Summary

Major release with critical security fixes and significant changes to upload handling and authentication behavior.

Key Highlights

  • Password changes now revoke other sessions
  • Stricter validation for SVG and XML uploads
  • Multipart uploads capped at 50MB by default
  • Client uploads stored in a per-upload folder

Breaking Changes

  • Password changes now revoke other sessions
  • Stricter validation for SVG and XML uploads
  • Multipart uploads are now capped at 50MB by default
  • Client uploads are stored in a per-upload folder
  • Form Builder defaults form submission read access to the admin collection

New Features

  • Security hardening
  • Password reset improvements
  • Upload path restructuring

Full Release Notes

## [v3.90.0](https://github.com/payloadcms/payload/compare/v3.89.0...v3.90.0) (2026-09-18)


⚠️ This release contains a set of critical security fixes. Please review the following notes and upgrade as soon as possible. **Even if none of the listed items below affect you, we still recommend updating as soon as possible.**

These notes only cover the behavior, configuration, and API-surface changes that projects may need to react to when upgrading. Exploit details, attack surface descriptions, and severity are intentionally omitted.



CVE and GHSA identifiers for the underlying issues are published separately.

## How to read this document

Every entry has: **Affected if you** (concrete conditions - if none apply, no action is required), and **Action required**.

**After upgrading:**

- Regenerate your payload types: `pnpm payload generate:types`
- **If you are using a relational database**, you will need to create and run a migration:

```sh
pnpm payload migrate:create <migration-name>
pnpm payload migrate
```

### Password changes now revoke other sessions

- No action needed.

### Password reset now clears lockouts; forgot-password is throttled

- Adds new `resetPasswordRequestedAt` field to user collections

- **Action required:**

  - Regenerate types: `pnpm payload generate:types`
  - If using a relational database, perform migration:

```sh
pnpm payload migrate:create add-reset-password-requested-at
pnpm payload migrate
```

### Scheduled publishing preserves the scheduling user's auth collection

**Affected if you:**

- Have publish or unpublish events queued when upgrading.
- Use multiple admin-capable auth collections.
- Queue `schedulePublish` jobs directly or depend on its generated task types.

**Action required:**

- Re-create pending scheduled publish and unpublish events after upgrading.
- Update custom queue code to pass `user: { relationTo, value }` (value is user ID).
- Regenerate committed Payload types if applicable.
- No database migration is required.

### Stricter validation for SVG and XML uploads

**Affected if you:**

- Upload SVG, XHTML, or XML-family files.
- Use direct client uploads, particularly with Azure or a custom integration.
- Depend on path components being retained in new filenames.

**Action required:**

- Verify affected upload and XML-delivery workflows.
- Ensure custom upload clients send the required metadata and returned headers.
- Set `allowRestrictedFileTypes: true` in the collections `upload` property if the previous behavior is explicitly required.

### Client uploads hardened across all adapters

**Affected if you:**

- Use any client upload adapters with `clientUploads: true` (S3, GCS, Azure, or custom).

**Action required:**

- If using GCS adapter, add `x-goog-if-generation-match` header to GCS CORS allowed headers.

### Azure containers default to private

**Affected if you:**

- Use the `@payloadcms/storage-azure` adapter with `allowContainerCreate: true`

**Action required:**

- If you wish to retain the previous behavior, set `containerAccess: 'blob'` in your Azure storage config.

### Client uploads are stored in a per-upload folder

**Affected if you:**

- Use `clientUploads` with a storage adapter and access stored files outside Payload (for example, by building paths from `prefix` and `filename`)
- Set document `prefix` values outside the adapter's collection `prefix`

**What changed:**

- New client uploads are stored at `<prefix>/<_objectKey>/<filename>`, including generated image sizes. `_objectKey` is a new field on upload collections. Existing files are not moved.
- New uploads and replacements are always stored inside the collection prefix. For example, a document prefix of `user-123` with collection prefix `media` is stored under `media/user-123/`.

**Action required:**

- Include `_objectKey` (when set) between the prefix and filename when building storage paths outside Payload.
- If using a relational database, the migration from the "After upgrading" step adds the `_objectKey` column.

### `disablePayloadAccessControl` no longer disables safe outbound fetch

**Affected if you:**

- Set `disableAccessControl: true` in your Payload config.

**Action required:**

- Configure a narrow `upload.skipSafeFetch` allowlist for trusted destinations
- Use `skipSafeFetch: true` only when every URL accepted by the collection is trusted.
- No action is required when files are retrieved from ordinary public endpoints.

### External file fetches require a trusted origin

**Affected if you:**

- Use `upload.disableLocalStorage: true` and rely on Payload fetching relative URLs whose endpoint requires a Payload session cookie.
- Implement `externalFileHeaderFilter`, particularly if it assumes it runs only once.
- Use non-HTTP(S) external-file URLs.

**Action required:**

- Configure `serverURL` or add the exact application origin to your CORS or CSRF configuration.
- Replace non-HTTP(S) external-file URLs with HTTP(S) URLs.
- Review `externalFileHeaderFilter` and use the optional context when headers need to vary by destination.

```diff
upload: {
  externalFileHeaderFilter: (headers, context) => {
+   if (!context?.isSameOrigin) {
+     delete headers.cookie
+     delete headers.authorization
+   }
+
    return headers
  },
}
```

### Uploaded filename hardening

**Affected if you:**

- Use a custom top-level prefix field on an upload collection as ordinary application data.
- Change an upload’s storage prefix without also replacing its file.

**Action required:**

- Rename a custom, non-storage prefix field or update it through trusted server-side code.
- Submit storage-prefix changes as part of a file replacement. Adapter-managed prefix fields do not require migration.

### Multipart uploads are now capped at 50MB by default

**Affected if you:**

- Use multipart uploads and rely on uploads being larger than 50MB

**Action required:**

- Raise the multipart cap if your application requires support for larger uploads for requests. Adjust the `requestSizeLimit` in your Payload config:

```diff
upload {
+  requestSizeLimit: 75 * 1024 * 1024, // Example 75 MiB for the complete multipart request
}
```

### Form Builder defaults form submission read access to the admin collection

**Affected if you:**

- Have more than one auth enabled where the secondary auth collections are expected to be able to read either:
  - form-submission docs
  - forms.emails array field

**Action required:**

- Supply an explicit formSubmissionOverrides.access or formOverrides.access to override the new defaults. If you already have overrides configured, they will continue to be respected.

### Stricter `where` validation for polymorphic joins

Polymorphic joins now apply complete `where` constraints and throw a `QueryError` when a filter is unsupported.

Unsupported filters include:

- Localized fields.
- Fields nested inside arrays or blocks.
- Paths traversing relationship, upload, or JSON fields, such as `owner.email`.
- The `near`, `within`, `intersects`, and `all` operators.
- The same field path having incompatible definitions across joined collections, such as number in one collection and text in another.

**Affected if you:**

- Use a join whose `collection` references multiple collections, and
- Apply an unsupported filter through:
  - A target collection's read access rule.
  - A join `where` constraint.
  - An Admin `baseFilter` or `baseListFilter` used by folder browsing.
- Use folders with folder-enabled collections whose read access or base filters contain one of these unsupported shapes.

**Action required:**

- Review `where` constraints and read access rules for every collection referenced by a polymorphic join.
- If using folders, review every folder-enabled collection's read access rules and Admin base filters.
- Rewrite unsupported filters to use direct, compatible fields while preserving the intended access restrictions.

### API keys are no longer readable after initial generation through UI or within Payload operations

If you wish to retain old behavior, set the following in your config:

```diff
auth: {
-  useAPIKey: true,
+  useAPIKey: {
+    reveal: true,
+  },
 }
```

### Lexical version bump

[Full Details](https://github.com/payloadcms/payload/pull/18148)

**No application changes or data migration are needed when using Payload's built-in rich text features.**

If you maintain custom rich text features, check that they still compile and that custom content loads, copies, and pastes correctly. Lexical removed some older APIs, tightened TypeScript types, and changed how custom nodes are loaded and copied. Tests that inspect the editor's HTML may also need updated selectors or snapshots because Lexical adds some internal markup.

Do not install lexical or @lexical/* yourself for use with Payload. Remove any direct dependencies you added for the editor and use Payload's re-exports from `@payloadcms/richtext-lexical/lexical` and `@payloadcms/richtext-lexical/lexical/*`. Payload supplies the matching versions; mixing versions can break the editor. This is the existing [custom feature requirement](https://payloadcms.com/docs/rich-text/custom-features).

Related Repositories