Compare commits

...

120 Commits

Author SHA1 Message Date
Ilham Saputrajati
27cfe775dd docs: missing comma in custom-strategies example (#13701)
Fix missing comma at custom-strategies example
2025-09-06 05:25:51 +00:00
Cedric Delacombaz
24e436bfa8 fix(richtext-lexical): fix picture closing tag in html converter (#13100)
# Fixing invalid html from convertLexicalToHTML with UploadConverter

## What?

When using the [convertLexicalToHTML
function](https://github.com/payloadcms/payload/blob/main/packages/richtext-lexical/src/features/converters/lexicalToHtml/sync/index.ts#L33C17-L33C37)
to convert richtext into html, the resulting html is incorrect when an
upload is present.

## Why?

The error is within the UploadHTMLConverter, as you can see in my
commits.

The picture closing tag has a Dollar sign in it `</picture$>`, which is
invalid html. This results in the picture not closing at the right
place. The picture element is not only wrapping the img tag, but also
all following content of that richtext field.

> I suppose that dollar sign ended up there due to auto rename tags from
IDE.

```ts
import { convertLexicalToHTML } from "@payloadcms/richtext-lexical/html";
const html = convertLexicalToHTML({ data: content });
```

**Example before fix**

```html
<div class="payload-richtext">
  <h2>Title</h2>
  <p>description</p>
  <picture>
      <img alt="media.png" height="400" src="https://some.url/storage/v1/object/public/media/media.png" width="684">
    <p>Some more text</p>
  </picture>
</div>
```

**Example after fix**

```html
<div class="payload-richtext">
  <h2>Title</h2>
  <p>description</p>
  <picture>
      <img alt="media.png" height="400" src="https://some.url/storage/v1/object/public/media/media.png" width="684">
  </picture>
  <p>Some more text</p>
</div>
```
2025-09-06 05:24:56 +00:00
Said Akhrarov
bbcdea5450 fix(next): display deleted relations and uploads in version diff views (#12955)
This PR fixes an issue where `relationship` and `upload` fields that had
their document-counterpart deleted would cause a runtime error. This
happens because the version diff components expected a populated
document instead of an id.

Two e2e tests were also added to the existing suite to test that the
diff view is reachable even after deletions.

### Why?
To prevent a runtime error when viewing diffs for documents that have
relationships to deleted documents.

### How?
Adjusting the diff view to accommodate deleted document relations.

Fixes #12915

Before:


[version-diff-collections-before.mp4](https://private-user-images.githubusercontent.com/20878653/458373129-745a5313-b85a-4ef0-a0d4-a13b52994f6f.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTA5Njc3NDEsIm5iZiI6MTc1MDk2NzQ0MSwicGF0aCI6Ii8yMDg3ODY1My80NTgzNzMxMjktNzQ1YTUzMTMtYjg1YS00ZWYwLWEwZDQtYTEzYjUyOTk0ZjZmLm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA2MjYlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNjI2VDE5NTA0MVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTUwOTUzMmJmYWI4YmM4ODZjYjhiNWVkOWE0NDgwODRiYjUxNjVkZmNiOWE2NWM3ODVhMTJiY2ViMGQ4MDE4NjYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.Of4Os9WCeOo_aN2kHNMShEgOc4-mYAwc41_E3YsX1tw)

After:


[versions-collections-after-Posts---Payload.webm](https://github.com/user-attachments/assets/ce4031da-b703-4944-857d-9ff627c58fdd)

Notes:
- I renamed `PopulatedRelationshipValue` to `RelationshipValue` as we
don't actually know if the value is populated. Such as in the case of
when the doc was deleted.
2025-09-06 04:52:15 +00:00
Aapo Laakkio
794bf8299c fix(next): version diff view shows correct document title in step nav (#13713)
This PR fixes two bugs in the version diff view SetStepNav component

### Bug 1: Document title isn't shown correctly in the step navigation
if the field of `useAsTitle` is nested inside a presentational field.

The StepNav shows the title of the document consistently throughout
every view except the version diff view. In the version diff view, the
document title is always `[Untitled]` if the field of `useAsTitle` is
nested inside presentational fields. Below is a video demo of the bug:


https://github.com/user-attachments/assets/23cb140a-b6d3-4d39-babf-5e4878651869

This happens because the fields of the collection/global aren't
flattened inside SetStepNav and thus the component is not accessing the
field data correctly. This results in the title being `null` causing the
fallback title to be shown.

### Bug 2: Step navigation shows the title of the version viewed, not
the current version

The StepNav component takes the title of the current version viewed.
This causes the second part of the navigation path to change between
versions which is inconsistent between other views and doesn't seem
intentional, although it could be. Below is a video of the bug with the
first bug fixed by flattening the fields:


https://github.com/user-attachments/assets/e5beb9b3-8e2e-4232-b1e5-5cce720e46b9

This happens due to the fact that the title is taken from the
`useAsTitle` field of the **viewed** version rather than the **current**
version. This bug is fixed by using the `useDocumentTitle` hook from the
ui package instead of passing the version's `useAsTitle` data down the
component tree. The final state of the step navigation is shown in the
following video:


https://github.com/user-attachments/assets/a69d5088-e7ee-43be-8f47-d9775d43dde9

I also added a test to test that the title part in the step navigation
stays consistent between versions and implicitly also tests that the
document title is shown correctly in the step nav if the field of
`useAsTitle` is a nested inside a presentational field.
2025-09-05 15:49:44 -07:00
Jarrod Flesch
9f0573d714 chore(ui): prevent loading orphaned documents when viewing root collection folders (#13684)
Root level collection folders should not display items, only folders. 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210821658736793
2025-09-05 18:22:18 -04:00
Jarrod Flesch
f288cf6a8f feat(ui): adds admin.autoRefresh root config property (#13682)
Adds the `admin.autoRefresh` property to the root config. This allows
users to stay logged and have their token always refresh in the
background without being prompted with the "Stay Logged In?" modal.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211114366468735
2025-09-05 17:05:18 -04:00
Alessio Gravili
03f7102433 fix: ensure client-side live preview correctly updates for globals (#13718)
Previously, we were sending incorrect API requests for globals.
Additionally, the global findOne endpoint did not respect the data
attribute.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211249618316704
2025-09-05 16:50:42 -04:00
Jarrod Flesch
09e3174834 fix(ui): consistent searchbar across folders and lists (#13712)
Search bar styling was inconsistent across folder and list views. This
re-uses the SearchBar component in the ListHeader component.
2025-09-05 16:40:11 -04:00
Anders Semb Hermansen
a4a0298435 fix: set X-Payload-HTTP-Method-Override as allowed cross origin header (#13717)
### What?

Set  X-Payload-HTTP-Method-Override as allowed cross origin header

### Why?

As of #13619 the live preview uses POST method to retrieve updated data.
When running cms and website on different origins, the cross origin
requires X-Payload-HTTP-Method-Override header to be allowed

### How?

Adding X-Payload-HTTP-Method-Override as a default allowed header
2025-09-05 12:59:37 -07:00
Alessio Gravili
7a8bcdf7ba feat(richtext-lexical): upgrade lexical from 0.34.0 to 0.35.0 (#13715)
This release upgrades the lexical dependency from 0.34.0 to 0.35.0.

If you installed lexical manually, update it to 0.35.0. Installing
lexical manually is not recommended, as it may break between updates,
and our re-exported versions should be used. See the [yellow banner
box](https://payloadcms.com/docs/rich-text/custom-features) for details.

If you still encounter richtext-lexical errors, do the following, in
this order:

- Delete node_modules
- Delete your lockfile (e.g. pnpm-lock.json)
- Reinstall your dependencies (e.g. pnpm install)

### Lexical Breaking Changes

The following Lexical releases describe breaking changes. We recommend
reading them if you're using Lexical APIs directly
(`@payloadcms/richtext-lexical/lexical/*`).

- [v.0.35.0](https://github.com/facebook/lexical/releases/tag/v0.35.0)
2025-09-05 18:48:36 +00:00
Alessio Gravili
6e203db33c feat(live-preview): client-side live preview: simplify population, support hooks and lexical block population (#13619)
Alternative solution to
https://github.com/payloadcms/payload/pull/11104. Big thanks to
@andershermansen and @GermanJablo for kickstarting work on a solution
and bringing this to our attention. This PR copies over the live-preview
test suite example from his PR.

Fixes https://github.com/payloadcms/payload/issues/5285,
https://github.com/payloadcms/payload/issues/6071 and
https://github.com/payloadcms/payload/issues/8277. Potentially fixes
#11801

This PR completely gets rid of our client-side live preview field
traversal + population and all logic related to it, and instead lets the
findByID endpoint handle it.

The data sent through the live preview message event is now passed to
findByID via the newly added `data` attribute. The findByID endpoint
will then use this data and run hooks on it (which run population),
instead of fetching the data from the database.

This new API basically behaves like a `/api/populate?data=` endpoint,
with the benefit that it runs all the hooks. Another use-case for it
will be rendering lexical data. Sometimes you may only have unpopulated
data available. This functionality allows you to then populate the
lexical portion of it on-the-fly, so that you can properly render it to
JSX while displaying images.

## Benefits
- a lot less code to maintain. No duplicative population logic
- much faster - one single API request instead of one request per
relationship to populate
- all payload features are now correctly supported (population and
hooks)
- since hooks are now running for client-side live preview, this means
the `lexicalHTML` field is now supported! This was a long-running issue
- this fixes a lot of population inconsistencies that we previously did
not know of. For example, it previously populated lexical and slate
relationships even if the data was saved in an incorrect format

## [Method Override
(POST)](https://payloadcms.com/docs/rest-api/overview#using-method-override-post)
change

The population request to the findByID endpoint is sent as a post
request, so that we can pass through the `data` without having to
squeeze it into the url params. To do that, it uses the
`X-Payload-HTTP-Method-Override` header.

Previously, this functionality still expected the data to be sent
through as URL search params - just passed to the body instead of the
URL. In this PR, I made it possible to pass it as JSON instead. This
means:

- the receiving endpoint will receive the data under `req.data` and is
not able to read it from the search params
- this means existing endpoints won't support this functionality unless
they also attempt to read from req.data.
- for the purpose of this PR, the findByID endpoint was modified to
support this behavior. This functionality is documented as it can be
useful for user-defined endpoints as well.

Passing data as json has the following benefits:

- it's more performant - no need to serialize and deserialize data to
search params via `qs-esm`. This is especially important here, as we are
passing large amounts of json data
- the current implementation was serializing the data incorrectly,
leading to incorrect data within nested lexical nodes

**Note for people passing their own live preview `requestHandler`:**
instead of sending a GET request to populate documents, you will now
need to send a POST request to the findByID endpoint and pass additional
headers. Additionally, you will need to send through the arguments as
JSON instead of search params and include `data` as an argument. Here is
the updated defaultRequestHandler for reference:

```ts
const defaultRequestHandler: CollectionPopulationRequestHandler = ({
  apiPath,
  data,
  endpoint,
  serverURL,
}) => {
  const url = `${serverURL}${apiPath}/${endpoint}`

  return fetch(url, {
    body: JSON.stringify(data),
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      'X-Payload-HTTP-Method-Override': 'GET',
    },
    method: 'POST',
  })
}
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211124793355068
  - https://app.asana.com/0/0/1211124793355066
2025-09-05 14:40:52 -04:00
German Jablonski
0c44c3bdd9 fix(richtext-lexical): add internationalization support for default style label in textStateFeature (#13662)
Fixes #13470

Output: 

<img width="344" height="176" alt="image"
src="https://github.com/user-attachments/assets/6a966e19-0a03-4252-9afa-b8149a95565b"
/>
2025-09-05 17:51:33 +01:00
Yazal Ulloa
008a52d8ec fix(templates): URI encode the cacheTag in getMediaUrl utility - Website template (#13558)
### What?
Encode the cacheTag in the ImageMedia component

### Why?

In the website template, media is render using the updatedAt field as a
cacheTag, this value causes an InvalidQueryStringException when
deploying to Cloudfront

### How?
Uses encodeURIComponent on encode the date value of updatedAt


Fixes https://github.com/payloadcms/payload/issues/13557
2025-09-04 17:45:38 +00:00
Patrik
7e98fbf78e fix(ui): cannot filter by virtual relationship fields in WhereBuilder (#13686)
### What?
- Fixed an issue where virtual relationship fields (`virtual: string`,
e.g. `post.title`) could not be used in the WhereBuilder filter
dropdown.
- Ensured regular virtual fields (`virtual: true`) are still excluded
since they are not backed by database fields.

### Why?
Previously, attempting to filter by a virtual relationship caused
runtime errors because the field was treated as a plain text field. At
the same time, non-queryable virtuals needed to remain excluded to avoid
invalid queries.

### How?
- Updated `reduceFieldsToOptions` to recognize `virtual: string` fields
and map them to their underlying path while keeping their declared type
and operators.
- Continued to filter out `virtual: true` fields in the same guard used
for hidden/disabled fields.
2025-09-04 08:17:10 -07:00
Elliot DeNolf
d109b44856 chore: add AGENTS.md (#13688)
Adds an [`AGENTS.md`](https://agents.md) file.
2025-09-04 16:11:17 +01:00
Patrik
5146fc865f fix(ui): undefined permissions passed in create-first-user view (#13671)
### What?

In the create-first-user view, fields like `richText` were being marked
as `readOnly: true` because they had no permissions entry in the
permissions map.

### Why?

The view was passing an incomplete `docPermissions` object. 

When a field had no entry in `docPermissions.fields`, `renderField`
received `permissions: undefined`, which was interpreted as denied
access.

This caused fields (notably `richText`) to default to read-only even
though the user should have full access when creating the first user.

### How?

- Updated the create-first-user view to always pass a complete
`docPermissions` object.
- Default all fields in the user collection to `{ create: true, read:
true, update: true }`.
- Ensures every field is explicitly granted full access during the
first-user flow.
- Keeps the `renderField` logic unchanged and aligned with Payload’s
permission model.

Fixes #13612 

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211211792037939
2025-09-03 14:16:49 -07:00
Jarrod Flesch
d9e183242c fix: update user session on reset (#13667)
Fixes https://github.com/payloadcms/payload/issues/13637

Adds session logic to password reset so the user is logged in after
resetting the password, similar to how it works when useSessions is
false.
2025-09-03 17:00:02 -04:00
Aapo Laakkio
7794541af3 fix(ui): don't populate on auto save (#13649)
Edited by @jacobsfletch

Autosave is run with default depth, causing form state and the document
info context to be inconsistent across the load/autosave/save lifecycle.

For example, when loading the app, the document is queried with `depth:
0` and relationships are _not_ populated. Same with save/draft/publish.
Autosave, on the other hand, populates these relationships, causing the
data to temporarily change in shape in between these events.

Here's an example:


https://github.com/user-attachments/assets/153ea112-7591-4f54-9216-575322f4edbe

Now, autosave runs with `depth: 0` as expected, consistent with the
other events of the document lifecycle.

**Plus this is a huge performance improvement.**

Fixes #13643 and #13192.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211224908421570

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-09-03 16:59:36 -04:00
German Jablonski
1a1696d9ae feat(richtext-lexical): upgrade lexical from 0.28.0 to 0.34.0 (#13622)
Fixes #13386

Below I write a clarification to copy and paste into the release note,
based on our latest upgrade of Lexical [in
v3.29.0](https://github.com/payloadcms/payload/releases/tag/v3.29.0).

## Important
This release upgrades the lexical dependency from 0.28.0 to 0.34.0.

If you installed lexical manually, update it to 0.34.0. Installing
lexical manually is not recommended, as it may break between updates,
and our re-exported versions should be used. See the [yellow banner
box](https://payloadcms.com/docs/rich-text/custom-features) for details.

If you still encounter richtext-lexical errors, do the following, in
this order:

- Delete node_modules
- Delete your lockfile (e.g. pnpm-lock.json)
- Reinstall your dependencies (e.g. pnpm install)

### Lexical Breaking Changes

The following Lexical releases describe breaking changes. We recommend
reading them if you're using Lexical APIs directly
(`@payloadcms/richtext-lexical/lexical/*`).

- [v.0.33.0](https://github.com/facebook/lexical/releases/tag/v0.33.0)
- [v.0.30.0](https://github.com/facebook/lexical/releases/tag/v0.30.0)
- [v.0.29.0](https://github.com/facebook/lexical/releases/tag/v0.29.0)

___

TODO:
- [x] https://github.com/facebook/lexical/pull/7719
- [x] https://github.com/facebook/lexical/pull/7362
- [x] https://github.com/facebook/lexical/pull/7707
- [x] https://github.com/facebook/lexical/pull/7388
- [x] https://github.com/facebook/lexical/pull/7357
- [x] https://github.com/facebook/lexical/pull/7352
- [x] https://github.com/facebook/lexical/pull/7472
- [x] https://github.com/facebook/lexical/pull/7556
- [x] https://github.com/facebook/lexical/pull/7417
- [x] https://github.com/facebook/lexical/pull/1036
- [x] https://github.com/facebook/lexical/pull/7509
- [x] https://github.com/facebook/lexical/pull/7693
- [x] https://github.com/facebook/lexical/pull/7408
- [x] https://github.com/facebook/lexical/pull/7450
- [x] https://github.com/facebook/lexical/pull/7415
- [x] https://github.com/facebook/lexical/pull/7368
- [x] https://github.com/facebook/lexical/pull/7372
- [x] https://github.com/facebook/lexical/pull/7572
- [x] https://github.com/facebook/lexical/pull/7558
- [x] https://github.com/facebook/lexical/pull/7613
- [x] https://github.com/facebook/lexical/pull/7405
- [x] https://github.com/facebook/lexical/pull/7420
- [x] https://github.com/facebook/lexical/pull/7662

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211202581885926
2025-09-03 18:49:10 +00:00
Jacob Fletcher
b8d7ccb4dc fix(ui): use consistent row ids when duplicating array and block rows (#13679)
Fixes #13653.

Duplicating array rows causes phantom rows to appear. This is because
when duplicate the row locally, we use inconsistent row IDs, e.g. the
`array.rows[0].id` does not match its `array.0.id` counterpart. This
causes form state to lose the reference to the existing row, which the
server interprets as new row as of #13551.

Before:


https://github.com/user-attachments/assets/9f7efc59-ebd9-4fbb-b643-c22d4d3140a3

After:


https://github.com/user-attachments/assets/188db823-4ee5-4757-8b89-751c8d978ad9

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211210023936585
2025-09-03 14:29:39 -04:00
Patrik
be47f65b7c fix: prevent enabling trash on folders (#13675)
### What?

This PR updates the folder configuration types to explicitly omit the
`trash` option from `CollectionConfig` when used for folders.

### Why?

Currently, only documents are designed to support trashing. Allowing
folders to be trashed introduces inconsistencies, since removing a
folder does not properly remove its references from associated
documents.

By omitting `trash` from folder configurations, we prevent accidental
use of unsupported behavior until proper design and handling for folder
trashing is implemented.

### How?

- Updated `RootFoldersConfiguration.collectionOverrides` type to use
`Omit<CollectionConfig, 'trash'>`
- Ensures `trash` cannot be enabled on folders
2025-09-03 11:04:48 -07:00
Jessica Rynkar
0f6d748365 feat: adds new experimental.localizeStatus option (#13207)
### What?
Adds a new `experimental.localizeStatus` config option, set to `false`
by default. When `true`, the admin panel will display the document
status based on the *current locale* instead of the _latest_ overall
status. Also updates the edit view to only show a `changed` status when
`autosave` is enabled.

### Why?
Showing the status for the current locale is more accurate and useful in
multi-locale setups. This update will become default behavior, able to
be opted in by setting `experimental.localizeStatus: true` in the
Payload config. This option will become depreciated in V4.

### How?
When `localizeStatus` is `true`, we store the localized status in a new
`localeStatus` field group within version data. The admin panel then
reads from this field to display the correct status for the current
locale.

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-09-03 10:27:08 +01:00
Alessio Gravili
a11586811e fix(ui): field.admin.condition data attribute missing document ID when document is being edited (#13676)
Fixes https://github.com/payloadcms/payload/issues/10379

During form state requests, the passed `data` did not have access to the
document ID. This was because the data we use came from the client,
passed as an argument. The client did not pass data that included the
document ID.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211203844178567
2025-09-03 01:14:07 +00:00
German Jablonski
9b109339ee fix(ui): await for publish success to update the UI (#13673)
Fixes #13664

Before:


https://github.com/user-attachments/assets/083577f5-9006-4b35-9799-3df704ed84a8

After:


https://github.com/user-attachments/assets/70a89a5b-bed4-447c-94f6-57bf3dbd2a70



---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211210023936582
2025-09-02 21:10:25 +01:00
German Jablonski
4e972c3fe2 docs(plugin-form-builder): add RadioField documentation (#13529)
### What?
Adds comprehensive documentation for the RadioField component in the
form builder plugin documentation.

### Why?
Addresses review feedback from #11908 noting that the RadioField was not
documented after being exported for end-user use.

### How?
- Added RadioField section with complete property documentation
- Added detailed Radio Options sub-section explaining option structure
- Updated Select field documentation for consistency (added missing
placeholder property and detailed options structure)
- Added radio field to configuration example to show all available
fields

Fixes the documentation gap identified in #11908 review comments.
2025-09-02 13:38:23 -04:00
dependabot[bot]
cfb70f06bb ci(deps): bump the github_actions group across 3 directories with 4 updates (#13654)
Bumps the github_actions group with 4 updates in the / directory:
[actions/checkout](https://github.com/actions/checkout),
[slackapi/slack-github-action](https://github.com/slackapi/slack-github-action),
[SethCohen/github-releases-to-discord](https://github.com/sethcohen/github-releases-to-discord)
and
[amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request).
Bumps the github_actions group with 1 update in the
/.github/actions/triage directory:
[actions/checkout](https://github.com/actions/checkout).
Bumps the github_actions group with 4 updates in the /.github/workflows
directory: [actions/checkout](https://github.com/actions/checkout),
[slackapi/slack-github-action](https://github.com/slackapi/slack-github-action),
[SethCohen/github-releases-to-discord](https://github.com/sethcohen/github-releases-to-discord)
and
[amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request).

Updates `actions/checkout` from 4 to 5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
<li>Prepare v5.0.0 release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2238">actions/checkout#2238</a></li>
</ul>
<h2>⚠️ Minimum Compatible Runner Version</h2>
<p><strong>v2.327.1</strong><br />
<a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<p>Make sure your runner is updated to this version or newer to use this
release.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v5.0.0">https://github.com/actions/checkout/compare/v4...v5.0.0</a></p>
<h2>v4.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
<li>Prepare release v4.3.0 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2237">actions/checkout#2237</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/motss"><code>@​motss</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li><a href="https://github.com/mouismail"><code>@​mouismail</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li><a href="https://github.com/benwells"><code>@​benwells</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v4.3.0">https://github.com/actions/checkout/compare/v4...v4.3.0</a></p>
<h2>v4.2.2</h2>
<h2>What's Changed</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.1...v4.2.2">https://github.com/actions/checkout/compare/v4.2.1...v4.2.2</a></p>
<h2>v4.2.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Jcambass"><code>@​Jcambass</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1919">actions/checkout#1919</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.0...v4.2.1">https://github.com/actions/checkout/compare/v4.2.0...v4.2.1</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>V5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>V4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a
href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>,
<a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
<li>Pin actions/checkout's own workflows to a known, good, stable
version. by <a href="https://github.com/jww3"><code>@​jww3</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1776">actions/checkout#1776</a></li>
</ul>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
<h2>v4.1.5</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
<li>README: Suggest <code>user.email</code> to be
<code>41898282+github-actions[bot]@users.noreply.github.com</code> by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1707">actions/checkout#1707</a></li>
</ul>
<h2>v4.1.4</h2>
<ul>
<li>Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1692">actions/checkout#1692</a></li>
<li>Add dependabot config by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1688">actions/checkout#1688</a></li>
<li>Bump the minor-actions-dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1693">actions/checkout#1693</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1643">actions/checkout#1643</a></li>
</ul>
<h2>v4.1.3</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="08c6903cd8"><code>08c6903</code></a>
Prepare v5.0.0 release (<a
href="https://redirect.github.com/actions/checkout/issues/2238">#2238</a>)</li>
<li><a
href="9f265659d3"><code>9f26565</code></a>
Update actions checkout to use node 24 (<a
href="https://redirect.github.com/actions/checkout/issues/2226">#2226</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />

Updates `slackapi/slack-github-action` from 2.1.0 to 2.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/slackapi/slack-github-action/releases">slackapi/slack-github-action's
releases</a>.</em></p>
<blockquote>
<h2>Slack Send v2.1.1</h2>
<h2>What's Changed</h2>
<p>This release fixes an issue where substituted variables might've
broken valid JSON or YAML parsings when using the
<code>payload-file-path</code> input option.</p>
<h3>🐛 Bug fixes</h3>
<ul>
<li>fix: parse provided payloads before replacing templated variables in
<a
href="https://redirect.github.com/slackapi/slack-github-action/pull/449">slackapi/slack-github-action#449</a>
- Thanks <a
href="https://github.com/zimeg"><code>@​zimeg</code></a>!</li>
</ul>
<h3>📚 Documentation</h3>
<ul>
<li>docs: fix channel mention formatting in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/447">slackapi/slack-github-action#447</a>
- Thanks <a
href="https://github.com/mwbrooks"><code>@​mwbrooks</code></a>!</li>
<li>docs: remove links to pages that are no longer referenced in
markdown in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/459">slackapi/slack-github-action#459</a>
- Thanks <a
href="https://github.com/zimeg"><code>@​zimeg</code></a>!</li>
</ul>
<h3>🤖 Dependencies</h3>
<ul>
<li>build(deps): bump undici from 5.28.5 to 5.29.0 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/442">slackapi/slack-github-action#442</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps): bump codecov/codecov-action from 5.4.2 to 5.4.3 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/443">slackapi/slack-github-action#443</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps-dev): bump mocha from 11.1.0 to 11.5.0 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/450">slackapi/slack-github-action#450</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps): bump <code>@​actions/github</code> from 6.0.0 to 6.0.1
in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/451">slackapi/slack-github-action#451</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps-dev): bump <code>@​types/node</code> from 22.15.3 to
22.15.29 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/452">slackapi/slack-github-action#452</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps): bump <code>@​slack/web-api</code> from 7.9.1 to 7.9.2
in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/453">slackapi/slack-github-action#453</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps): bump <code>@​slack/web-api</code> from 7.9.2 to 7.9.3
in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/462">slackapi/slack-github-action#462</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps): bump axios from 1.9.0 to 1.10.0 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/465">slackapi/slack-github-action#465</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps-dev): bump <code>@​types/node</code> from 22.15.29 to
24.0.3 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/466">slackapi/slack-github-action#466</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps-dev): bump mocha from 11.5.0 to 11.7.1 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/468">slackapi/slack-github-action#468</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps-dev): bump mocha-suppress-logs from 0.5.1 to 0.6.0 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/469">slackapi/slack-github-action#469</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps-dev): bump sinon from 20.0.0 to 21.0.0 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/471">slackapi/slack-github-action#471</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps-dev): bump <code>@​types/node</code> from 24.0.3 to
24.0.8 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/472">slackapi/slack-github-action#472</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
<li>build(deps-dev): bump <code>@​biomejs/biome</code> from 1.9.4 to
2.0.6 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/470">slackapi/slack-github-action#470</a>
- Thanks <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>!</li>
</ul>
<h3>🧰 Maintenance</h3>
<ul>
<li>ci: pin action hashes and escape variables with minimum permission
in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/441">slackapi/slack-github-action#441</a>
- Thanks <a
href="https://github.com/zimeg"><code>@​zimeg</code></a>!</li>
<li>build: create separate release branches for tagged releases on
publish in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/457">slackapi/slack-github-action#457</a>
- Thanks <a
href="https://github.com/zimeg"><code>@​zimeg</code></a>!</li>
<li>build: clone repository &quot;docs&quot; and configuration when
syncing project docs in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/467">slackapi/slack-github-action#467</a>
- Thanks <a
href="https://github.com/lukegalbraithrussell"><code>@​lukegalbraithrussell</code></a>!</li>
<li>chore(release): tag version 2.1.1 in <a
href="https://redirect.github.com/slackapi/slack-github-action/pull/474">slackapi/slack-github-action#474</a>
- Thanks <a
href="https://github.com/zimeg"><code>@​zimeg</code></a>!</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/slackapi/slack-github-action/compare/v2.1.0...v2.1.1">https://github.com/slackapi/slack-github-action/compare/v2.1.0...v2.1.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="91efab103c"><code>91efab1</code></a>
Release</li>
<li><a
href="b6f4640825"><code>b6f4640</code></a>
chore(release): tag version 2.1.1 (<a
href="https://redirect.github.com/slackapi/slack-github-action/issues/474">#474</a>)</li>
<li><a
href="d3dc61e5d1"><code>d3dc61e</code></a>
build(deps-dev): bump <code>@​biomejs/biome</code> from 1.9.4 to 2.0.6
(<a
href="https://redirect.github.com/slackapi/slack-github-action/issues/470">#470</a>)</li>
<li><a
href="f647c89261"><code>f647c89</code></a>
build(deps-dev): bump <code>@​types/node</code> from 24.0.3 to 24.0.8
(<a
href="https://redirect.github.com/slackapi/slack-github-action/issues/472">#472</a>)</li>
<li><a
href="e6fa63302e"><code>e6fa633</code></a>
build(deps-dev): bump sinon from 20.0.0 to 21.0.0 (<a
href="https://redirect.github.com/slackapi/slack-github-action/issues/471">#471</a>)</li>
<li><a
href="75b7822f87"><code>75b7822</code></a>
build(deps-dev): bump mocha-suppress-logs from 0.5.1 to 0.6.0 (<a
href="https://redirect.github.com/slackapi/slack-github-action/issues/469">#469</a>)</li>
<li><a
href="d7b6150e2a"><code>d7b6150</code></a>
build(deps-dev): bump mocha from 11.5.0 to 11.7.1 (<a
href="https://redirect.github.com/slackapi/slack-github-action/issues/468">#468</a>)</li>
<li><a
href="a7f5b68f29"><code>a7f5b68</code></a>
build: clone repository &quot;docs&quot; and configuration when syncing
project docs (#...</li>
<li><a
href="c69deab257"><code>c69deab</code></a>
build(deps-dev): bump <code>@​types/node</code> from 22.15.29 to 24.0.3
(<a
href="https://redirect.github.com/slackapi/slack-github-action/issues/466">#466</a>)</li>
<li><a
href="1d0943cb8c"><code>1d0943c</code></a>
build(deps): bump axios from 1.9.0 to 1.10.0 (<a
href="https://redirect.github.com/slackapi/slack-github-action/issues/465">#465</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/slackapi/slack-github-action/compare/v2.1.0...v2.1.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `SethCohen/github-releases-to-discord` from 1.16.2 to 1.19.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sethcohen/github-releases-to-discord/releases">SethCohen/github-releases-to-discord's
releases</a>.</em></p>
<blockquote>
<h2>v1.19.0</h2>
<h2><a
href="https://github.com/SethCohen/github-releases-to-discord/compare/v1.18.0...v1.19.0">1.19.0</a>
(2025-06-17)</h2>
<h3>Features</h3>
<ul>
<li><strong>tests:</strong> add Jest configuration and comprehensive
tests for utility functions in index.js to ensure functionality and
reliability (<a
href="0559b87ee8">0559b87</a>)</li>
</ul>
<h3>Miscellaneous</h3>
<ul>
<li>added updated dependencies (<a
href="067d2cb017">067d2cb</a>)</li>
<li><strong>package:</strong> update <code>@​actions/github</code>
dependency to version 6.0.1 and add Jest as a dev dependency with a test
script (<a
href="0559b87ee8">0559b87</a>)</li>
</ul>
<h2>v1.18.0</h2>
<h2><a
href="https://github.com/SethCohen/github-releases-to-discord/compare/v1.17.0...v1.18.0">1.18.0</a>
(2025-06-17)</h2>
<h3>Features</h3>
<ul>
<li><strong>index.js:</strong> enhance sendWebhook function to handle
rate limits with retries for improved reliability when sending requests
to Discord (<a
href="feb5a40237">feb5a40</a>)</li>
</ul>
<h3>Miscellaneous</h3>
<ul>
<li>remove unnecessary test file from .gitignore and add sample test
release JSON for local testing (<a
href="82d906cc6f">82d906c</a>)</li>
<li>update README for clarity and conciseness, improve formatting, and
add new sections for better user guidance (<a
href="82d906cc6f">82d906c</a>)</li>
</ul>
<h2>v1.17.0</h2>
<h2><a
href="https://github.com/SethCohen/github-releases-to-discord/compare/v1.16.2...v1.17.0">1.17.0</a>
(2025-06-17)</h2>
<h3>Features</h3>
<ul>
<li><strong>workflow:</strong> add GitHub Actions workflow to
automatically update SemVer tags on tag push events (<a
href="e768ce1023">e768ce1</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/SethCohen/github-releases-to-discord/blob/master/CHANGELOG.md">SethCohen/github-releases-to-discord's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/SethCohen/github-releases-to-discord/compare/v1.18.0...v1.19.0">1.19.0</a>
(2025-06-17)</h2>
<h3>Features</h3>
<ul>
<li><strong>tests:</strong> add Jest configuration and comprehensive
tests for utility functions in index.js to ensure functionality and
reliability (<a
href="0559b87ee8">0559b87</a>)</li>
</ul>
<h3>Miscellaneous</h3>
<ul>
<li>added updated dependencies (<a
href="067d2cb017">067d2cb</a>)</li>
<li><strong>package:</strong> update <code>@​actions/github</code>
dependency to version 6.0.1 and add Jest as a dev dependency with a test
script (<a
href="0559b87ee8">0559b87</a>)</li>
</ul>
<h2><a
href="https://github.com/SethCohen/github-releases-to-discord/compare/v1.17.0...v1.18.0">1.18.0</a>
(2025-06-17)</h2>
<h3>Features</h3>
<ul>
<li><strong>index.js:</strong> enhance sendWebhook function to handle
rate limits with retries for improved reliability when sending requests
to Discord (<a
href="feb5a40237">feb5a40</a>)</li>
</ul>
<h3>Miscellaneous</h3>
<ul>
<li>remove unnecessary test file from .gitignore and add sample test
release JSON for local testing (<a
href="82d906cc6f">82d906c</a>)</li>
<li>update README for clarity and conciseness, improve formatting, and
add new sections for better user guidance (<a
href="82d906cc6f">82d906c</a>)</li>
</ul>
<h2><a
href="https://github.com/SethCohen/github-releases-to-discord/compare/v1.16.2...v1.17.0">1.17.0</a>
(2025-06-17)</h2>
<h3>Features</h3>
<ul>
<li><strong>workflow:</strong> add GitHub Actions workflow to
automatically update SemVer tags on tag push events (<a
href="e768ce1023">e768ce1</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b96a33520f"><code>b96a335</code></a>
chore(master): release 1.19.0 (<a
href="https://redirect.github.com/sethcohen/github-releases-to-discord/issues/50">#50</a>)</li>
<li><a
href="067d2cb017"><code>067d2cb</code></a>
chore: added updated dependencies</li>
<li><a
href="0559b87ee8"><code>0559b87</code></a>
feat(tests): add Jest configuration and comprehensive tests for utility
funct...</li>
<li><a
href="de60879a86"><code>de60879</code></a>
chore(master): release 1.18.0 (<a
href="https://redirect.github.com/sethcohen/github-releases-to-discord/issues/49">#49</a>)</li>
<li><a
href="82d906cc6f"><code>82d906c</code></a>
chore: update README for clarity and conciseness, improve formatting,
and add...</li>
<li><a
href="feb5a40237"><code>feb5a40</code></a>
feat(index.js): enhance sendWebhook function to handle rate limits with
retri...</li>
<li><a
href="9fe781fdc7"><code>9fe781f</code></a>
Add custom url (<a
href="https://redirect.github.com/sethcohen/github-releases-to-discord/issues/44">#44</a>)</li>
<li><a
href="74ded4247d"><code>74ded42</code></a>
Add option to strip PR and commit links (<a
href="https://redirect.github.com/sethcohen/github-releases-to-discord/issues/48">#48</a>)</li>
<li><a
href="e1dc0826fe"><code>e1dc082</code></a>
chore(master): release 1.17.0 (<a
href="https://redirect.github.com/sethcohen/github-releases-to-discord/issues/47">#47</a>)</li>
<li><a
href="e768ce1023"><code>e768ce1</code></a>
feat(workflow): add GitHub Actions workflow to automatically update
SemVer ta...</li>
<li>See full diff in <a
href="https://github.com/sethcohen/github-releases-to-discord/compare/v1.16.2...v1.19.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `amannn/action-semantic-pull-request` from 5 to 6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/amannn/action-semantic-pull-request/releases">amannn/action-semantic-pull-request's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.5.3...v6.0.0">6.0.0</a>
(2025-08-13)</h2>
<h3>⚠ BREAKING CHANGES</h3>
<ul>
<li>Upgrade action to use Node.js 24 and ESM (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/287">#287</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>Upgrade action to use Node.js 24 and ESM (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/287">#287</a>)
(<a
href="bc0c9a79ab">bc0c9a7</a>)</li>
</ul>
<h2>v5.5.3</h2>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.5.2...v5.5.3">5.5.3</a>
(2024-06-28)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Bump <code>braces</code> dependency (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/269">#269</a>.
by <a href="https://github.com/EelcoLos"><code>@​EelcoLos</code></a>)
(<a
href="2d952a1bf9">2d952a1</a>)</li>
</ul>
<h2>v5.5.2</h2>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.5.1...v5.5.2">5.5.2</a>
(2024-04-24)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Bump tar from 6.1.11 to 6.2.1 (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/262">#262</a>
by <a href="https://github.com/EelcoLos"><code>@​EelcoLos</code></a>)
(<a
href="9a90d5a5ac">9a90d5a</a>)</li>
</ul>
<h2>v5.5.1</h2>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.5.0...v5.5.1">5.5.1</a>
(2024-04-24)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Bump ip from 2.0.0 to 2.0.1 (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/263">#263</a>
by <a href="https://github.com/EelcoLos"><code>@​EelcoLos</code></a>)
(<a
href="5e7e9acca3">5e7e9ac</a>)</li>
</ul>
<h2>v5.5.0</h2>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.4.0...v5.5.0">5.5.0</a>
(2024-04-23)</h2>
<h3>Features</h3>
<ul>
<li>Add outputs for <code>type</code>, <code>scope</code> and
<code>subject</code> (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/261">#261</a>
by <a href="https://github.com/bcaurel"><code>@​bcaurel</code></a>) (<a
href="b05f5f6423">b05f5f6</a>)</li>
</ul>
<h2>v5.4.0</h2>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.3.0...v5.4.0">5.4.0</a>
(2023-11-03)</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md">amannn/action-semantic-pull-request's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.2.0...v5.3.0">5.3.0</a>
(2023-09-25)</h2>
<h3>Features</h3>
<ul>
<li>Use Node.js 20 in action (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/240">#240</a>)
(<a
href="4c0d5a21fc">4c0d5a2</a>)</li>
</ul>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.1.0...v5.2.0">5.2.0</a>
(2023-03-16)</h2>
<h3>Features</h3>
<ul>
<li>Update dependencies by <a
href="https://github.com/EelcoLos"><code>@​EelcoLos</code></a> (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/229">#229</a>)
(<a
href="e797448a07">e797448</a>)</li>
</ul>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.0.2...v5.1.0">5.1.0</a>
(2023-02-10)</h2>
<h3>Features</h3>
<ul>
<li>Add regex support to <code>scope</code> and
<code>disallowScopes</code> configuration (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/226">#226</a>)
(<a
href="403a6f8924">403a6f8</a>)</li>
</ul>
<h3><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.0.1...v5.0.2">5.0.2</a>
(2022-10-17)</h3>
<h3>Bug Fixes</h3>
<ul>
<li>Upgrade <code>@actions/core</code> to avoid deprecation warnings (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/208">#208</a>)
(<a
href="91f4126c9e">91f4126</a>)</li>
</ul>
<h3><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5.0.0...v5.0.1">5.0.1</a>
(2022-10-14)</h3>
<h3>Bug Fixes</h3>
<ul>
<li>Upgrade GitHub Action to use Node v16 (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/207">#207</a>)
(<a
href="6282ee339b">6282ee3</a>)</li>
</ul>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v4.6.0...v5.0.0">5.0.0</a>
(2022-10-11)</h2>
<h3>⚠ BREAKING CHANGES</h3>
<ul>
<li>Enum options need to be newline delimited (to allow whitespace
within them) (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/205">#205</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>Enum options need to be newline delimited (to allow whitespace
within them) (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/205">#205</a>)
(<a
href="c906fe1e5a">c906fe1</a>)</li>
</ul>
<h2><a
href="https://github.com/amannn/action-semantic-pull-request/compare/v4.5.0...v4.6.0">4.6.0</a>
(2022-09-26)</h2>
<h3>Features</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="48f256284b"><code>48f2562</code></a>
chore: Release 6.1.1 [skip ci]</li>
<li><a
href="800da4c97f"><code>800da4c</code></a>
fix: Parse <code>headerPatternCorrespondence</code> properly (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/295">#295</a>)</li>
<li><a
href="677b89571e"><code>677b895</code></a>
test: Fix broken test</li>
<li><a
href="24e6f016c1"><code>24e6f01</code></a>
ci: Fix permissions for tagger</li>
<li><a
href="7f33ba7922"><code>7f33ba7</code></a>
chore: Release 6.1.0 [skip ci]</li>
<li><a
href="afa4edb1c4"><code>afa4edb</code></a>
fix: Remove trailing whitespace from &quot;unknown release type&quot;
error message (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/291">#291</a>)</li>
<li><a
href="a30288bf13"><code>a30288b</code></a>
feat: Support providing regexps for types (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/292">#292</a>)</li>
<li><a
href="a46a7c8dc4"><code>a46a7c8</code></a>
build: Move Vitest to <code>devDependencies</code> (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/290">#290</a>)</li>
<li><a
href="fdd4d3ddf6"><code>fdd4d3d</code></a>
chore: Release 6.0.1 [skip ci]</li>
<li><a
href="58e4ab40f5"><code>58e4ab4</code></a>
fix: Actually execute action (<a
href="https://redirect.github.com/amannn/action-semantic-pull-request/issues/289">#289</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/amannn/action-semantic-pull-request/compare/v5...v6">compare
view</a></li>
</ul>
</details>
<br />

Updates `actions/checkout` from 4 to 5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
<li>Prepare v5.0.0 release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2238">actions/checkout#2238</a></li>
</ul>
<h2>⚠️ Minimum Compatible Runner Version</h2>
<p><strong>v2.327.1</strong><br />
<a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<p>Make sure your runner is updated to this version or newer to use this
release.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v5.0.0">https://github.com/actions/checkout/compare/v4...v5.0.0</a></p>
<h2>v4.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
<li>Prepare release v4.3.0 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2237">actions/checkout#2237</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/motss"><code>@​motss</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li><a href="https://github.com/mouismail"><code>@​mouismail</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li><a href="https://github.com/benwells"><code>@​benwells</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v4.3.0">https://github.com/actions/checkout/compare/v4...v4.3.0</a></p>
<h2>v4.2.2</h2>
<h2>What's Changed</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.1...v4.2.2">https://github.com/actions/checkout/compare/v4.2.1...v4.2.2</a></p>
<h2>v4.2.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Jcambass"><code>@​Jcambass</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1919">actions/checkout#1919</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.0...v4.2.1">https://github.com/actions/checkout/compare/v4.2.0...v4.2.1</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>V5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>V4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a
href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>,
<a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
<li>Pin actions/checkout's own workflows to a known, good, stable
version. by <a href="https://github.com/jww3"><code>@​jww3</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1776">actions/checkout#1776</a></li>
</ul>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
<h2>v4.1.5</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
<li>README: Suggest <code>user.email</code> to be
<code>41898282+github-actions[bot]@users.noreply.github.com</code> by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1707">actions/checkout#1707</a></li>
</ul>
<h2>v4.1.4</h2>
<ul>
<li>Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1692">actions/checkout#1692</a></li>
<li>Add dependabot config by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1688">actions/checkout#1688</a></li>
<li>Bump the minor-actions-dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1693">actions/checkout#1693</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1643">actions/checkout#1643</a></li>
</ul>
<h2>v4.1.3</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="08c6903cd8"><code>08c6903</code></a>
Prepare v5.0.0 release (<a
href="https://redirect.github.com/actions/checkout/issues/2238">#2238</a>)</li>
<li><a
href="9f265659d3"><code>9f26565</code></a>
Update actions checkout to use node 24 (<a
href="https://redirect.github.com/actions/checkout/issues/2226">#2226</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />

Updates `actions/checkout` from 4 to 5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
<li>Prepare v5.0.0 release by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2238">actions/checkout#2238</a></li>
</ul>
<h2>⚠️ Minimum Compatible Runner Version</h2>
<p><strong>v2.327.1</strong><br />
<a
href="https://github.com/actions/runner/releases/tag/v2.327.1">Release
Notes</a></p>
<p>Make sure your runner is updated to this version or newer to use this
release.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v5.0.0">https://github.com/actions/checkout/compare/v4...v5.0.0</a></p>
<h2>v4.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
<li>Prepare release v4.3.0 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2237">actions/checkout#2237</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/motss"><code>@​motss</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li><a href="https://github.com/mouismail"><code>@​mouismail</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li><a href="https://github.com/benwells"><code>@​benwells</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li><a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4...v4.3.0">https://github.com/actions/checkout/compare/v4...v4.3.0</a></p>
<h2>v4.2.2</h2>
<h2>What's Changed</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.1...v4.2.2">https://github.com/actions/checkout/compare/v4.2.1...v4.2.2</a></p>
<h2>v4.2.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Jcambass"><code>@​Jcambass</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/checkout/pull/1919">actions/checkout#1919</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v4.2.0...v4.2.1">https://github.com/actions/checkout/compare/v4.2.0...v4.2.1</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>V5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>V4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a
href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>,
<a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
<li>Pin actions/checkout's own workflows to a known, good, stable
version. by <a href="https://github.com/jww3"><code>@​jww3</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1776">actions/checkout#1776</a></li>
</ul>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
<h2>v4.1.5</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
<li>README: Suggest <code>user.email</code> to be
<code>41898282+github-actions[bot]@users.noreply.github.com</code> by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1707">actions/checkout#1707</a></li>
</ul>
<h2>v4.1.4</h2>
<ul>
<li>Disable <code>extensions.worktreeConfig</code> when disabling
<code>sparse-checkout</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1692">actions/checkout#1692</a></li>
<li>Add dependabot config by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1688">actions/checkout#1688</a></li>
<li>Bump the minor-actions-dependencies group with 2 updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1693">actions/checkout#1693</a></li>
<li>Bump word-wrap from 1.2.3 to 1.2.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1643">actions/checkout#1643</a></li>
</ul>
<h2>v4.1.3</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="08c6903cd8"><code>08c6903</code></a>
Prepare v5.0.0 release (<a
href="https://redirect.github.com/actions/checkout/issues/2238">#2238</a>)</li>
<li><a
href="9f265659d3"><code>9f26565</code></a>
Update actions checkout to use node 24 (<a
href="https://redirect.github.com/actions/checkout/issues/2226">#2226</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/v4...v5">compare
view</a></li>
</ul>
</details>
<br />

Updates `slackapi/slack-github-action` from 2.1.0 to 2.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/slackapi/slack-github-action/releases">slackapi/slack-github-action's
releases</a>.</em></p>
<blockquote>
<h2>Slack Send v2.1.1</h2>
<h2>What's Changed</h2>
<p>This release fixes an issue...

_Description has been truncated_

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 10:00:09 -04:00
Jarrod Flesch
1c68ed5251 fix(ui): sidebar missing sticky top offset (#13652)
Regression from https://github.com/payloadcms/payload/pull/13340.
Incorrect fallback value for the `--sidebar-wrap-top` css variable was
used.

### Before
<img width="2896" height="1200" alt="CleanShot 2025-09-01 at 10 03
39@2x"
src="https://github.com/user-attachments/assets/caf054d2-ac61-4a96-b6a1-3981ce9f528f"
/>

### After
<img width="2894" height="1194" alt="CleanShot 2025-09-01 at 10 02
59@2x"
src="https://github.com/user-attachments/assets/1e13e084-6f62-47bb-88b3-921ba5f3ff10"
/>
2025-09-02 13:51:07 +00:00
Jarrod Flesch
65b3845d92 fix(plugin-multi-tenant): skip baseFilter if user has access to all tenants (#13633)
Fixes
https://github.com/payloadcms/payload/issues/13589#issuecomment-3234832478


### Problem
The baseFilter is being applied when a user has no tenant selected, is
assigned to tenants BUT also passes the `userHasAccessToAllTenants`
check.


### Fix
Do not apply the baseFilter when no tenant is selected and the user
passes the `userHasAccessToAllTenants` check.
2025-09-02 09:33:35 -04:00
reiv
917c66fe44 fix(db-sqlite): convert Date to ISO 8601 string in queries (#11694)
### What?
Restores the ability to query by `Date(...)` when using the `sqlite`
adapter.

### Why?
Queries involving JavaScript `Date`s return incorrect results because
they are not converted to ISO 8601 strings by the `sqlite` adapter.

### How?
Wraps comparison operators to convert `Date` parameters to ISO 8601
strings.

Fixes #11692

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-09-02 11:35:02 +01:00
Jessica Rynkar
ac691b675b fix(ui): should not show publish specific locale button when no localized fields exist (#13459)
### What?
Hides the `Publish in [specific locale]` button on collections and
globals when no localized fields are present.

### Why?
Currently, the `Publish in [specific locale]` shows on every
collection/global as long as `localization` is enabled in the Payload
config, however this is not necessary when the collection/global has no
localized fields.

### How?
Checks that localized fields exist prior to rendering the `Publish in
[specific locale]` button.

Fixes #13447

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-09-02 10:23:53 +01:00
Riley Langbein
fdab2712c0 feat(richtext-lexical): add options to hide block handles (#13647)
### What?

Currently the `DraggableBlockPlugin` and `AddBlockHandlePlugin`
components are automatically applied to every editor. For flexibility
purposes, we want to allow these to be optionally removed when needed.

### Why?

There are scenarios where you may want to enforce certain limitations on
an editor, such as only allowing a single line of text. The draggable
block element and add block button wouldn't play nicely with this
scenario.

Previously in order to do this, you needed to use custom css to hide the
elements, which still technically allows them to be accessible to the
end-user if they removed the CSS. This implementation ensures the
handlers are properly removed when not wanted.

### How?

Add `hideDraggableBlockElement` and `hideAddBlockButton` options to the
lexical `admin` property. When these are set to `true`, the
`DraggableBlockPlugin` and `AddBlockHandlePlugin` are not rendered to
the DOM.

Addresses #13636
2025-08-31 05:51:00 +00:00
Jacob Fletcher
a231a05b7c fix(plugin-nested-docs): prevent phantom breadcrumb row (#13628)
When saving a doc and regenerating the breadcrumbs array, a phantom row
will append itself to the end of the array on save. This is because of
fixes made in #13551 changed the way we merge array and block rows from
the server.

To fix this we need to ensure that row IDs are consistent across form
state invocations, i.e. the hooks that mutate the array rows _cannot_
discard the row IDs.

Before:


https://github.com/user-attachments/assets/db715801-b4fd-4114-b39b-8d9b37fad979

After:


https://github.com/user-attachments/assets/6da63a31-cd5d-43c1-a15e-caddbc540d56

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211175452200168
2025-08-28 16:12:47 -04:00
Jacob Fletcher
b99c324f1e perf(plugin-search): reindex collections in parallel, up to 80% faster (#13608)
Reindexing all collections in the search plugin was previously done in
sequence which is slow and risks timing out under certain network
conditions. By running these requests in parallel we are able to save
**on average ~80%** in compute time.

This test includes reindexing 2000 documents in total across 2
collections, 3 times over. The indexes themselves are relatively simple,
containing only a couple simple fields each, so the savings would only
increase with more complexity and/or more documents.

Before:
Attempt 1: 38434.87ms
Attempt 2: 47852.61ms
Attempt 3: 28407.79ms
Avg: 38231.75ms

After:
Attempt 1: 7834.29ms
Attempt 2: 7744.40ms
Attempt 3: 7918.58ms
Avg: 7832.42ms

Total savings: ~79.51%

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211162504205343
2025-08-28 16:12:35 -04:00
Patrik
426f99ca99 fix(ui): json field type ignoring editorOptions (#13630)
### What?

Fix `JSON` field so that it respects `admin.editorOptions` (e.g.
`tabSize`, `insertSpaces`, etc.), matching the behavior of the `code`
field. Also refactor `CodeEditor` to set indentation and whitespace
options per-model instead of globally.

### Why?

- Previously, the JSON field ignored `editorOptions` and always
serialized with spaces (`tabSize: 2`). This caused inconsistencies when
comparing JSON and code fields configured with the same options.
- Monaco’s global defaults were being overridden in a way that leaked
settings between editors, making per-field customization unreliable.

### How?

- Updated `JSON` field to extract `tabSize` from `editorOptions` and
pass it through consistently when serializing and mounting the editor.
- Refactored CodeEditor to:
  - Disable `detectIndentation` globally.
- Apply `insertSpaces`, `tabSize`, and `trimAutoWhitespace` on a
per-model basis inside onMount.
  - Preserve all other `editorOptions` as before.

Fixes #13583 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211177100283503

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-08-28 12:57:16 -07:00
Jarrod Flesch
4600c94cac fix(plugin-nested-docs): crumbs not syncing on non-versioned collections (#13629)
Fixes https://github.com/payloadcms/payload/issues/13563

When using the nested docs plugin with collections that do not have
drafts enabled. It was not syncing breadcrumbs from parent changes.

The root cause was returning early on any document that did not meet
`doc._status !== 'published'` check, which was **_always_** the case for
non-draft collections.

### Before
```ts
if (doc._status !== 'published') {
  return
}
```
### After
```ts
if (collection.versions.drafts && doc._status !== 'published') {
  return
}
```
2025-08-28 15:43:31 -04:00
Elliot DeNolf
c1b4960795 chore(release): v3.54.0 [skip ci] 2025-08-28 09:47:54 -04:00
Alessio Gravili
97c41ce0c5 refactor: deprecate job queue runHooks property (#13617)
Setting `runHooks: true` is already discouraged and will make the job
queue system a lot slower and less reliable. We have no test coverage
for this and it's additional code we need to maintain.

To further discourage using this property, this PR marks it as
deprecated and for removal in 4.0.
2025-08-27 21:32:06 -04:00
Alessio Gravili
e0ffada80b feat: support parallel job queue tasks, speed up task running (#13614)
Currently, attempting to run tasks in parallel will result in DB errors.

## Solution

The problem was caused due to inefficient db update calls. After each
task completes, we need to update the log array in the payload-jobs
collection. On postgres, that's a different table.

Currently, the update works the following way:
1. Nuke the table
2. Re-insert every single row, including the new one

This will throw db errors if multiple processes start doing that.
Additionally, due to conflicts, new log rows may be lost.

This PR makes use of the the [new db $push operation
](https://github.com/payloadcms/payload/pull/13453) we recently added to
atomically push a new log row to the database in a single round-trip.
This not only reduces the amount of db round trips (=> faster job queue
system) but allows multiple tasks to perform this db operation in
parallel, without conflicts.

## Problem

**Example:**

```ts
export const fastParallelTaskWorkflow: WorkflowConfig<'fastParallelTask'> = {
  slug: 'fastParallelTask',
  handler: async ({nlineTask }) => {
    const taskFunctions = []
    for (let i = 0; i < 20; i++) {
      const idx = i + 1
      taskFunctions.push(async () => {
        return await inlineTask(`parallel task ${idx}`, {
          input: {
            test: idx,
          },
          task: () => {
            return {
              output: {
                taskID: idx.toString(),
              },
            }
          },
        })
      })
    }

    await Promise.all(taskFunctions.map((f) => f()))
  },
}
```

On SQLite, this would throw the following error:

```bash
Caught error Error: UNIQUE constraint failed: payload_jobs_log.id
    at Object.next (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/libsql@0.4.7/node_modules/libsql/index.js:335:20)
    at Statement.all (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/libsql@0.4.7/node_modules/libsql/index.js:360:16)
    at executeStmt (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5/node_modules/@libsql/client/lib-cjs/sqlite3.js:285:34)
    at Sqlite3Client.execute (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5/node_modules/@libsql/client/lib-cjs/sqlite3.js:101:16)
    at /Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/libsql/session.ts:288:58
    at LibSQLPreparedQuery.queryWithCache (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/sqlite-core/session.ts:79:18)
    at LibSQLPreparedQuery.values (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/libsql/session.ts:286:21)
    at LibSQLPreparedQuery.all (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/libsql/session.ts:214:27)
    at QueryPromise.all (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/sqlite-core/query-builders/insert.ts:402:26)
    at QueryPromise.execute (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/sqlite-core/query-builders/insert.ts:414:40)
    at QueryPromise.then (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/query-promise.ts:31:15) {
  rawCode: 1555,
  code: 'SQLITE_CONSTRAINT_PRIMARYKEY',
  libsqlError: true
}
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211001438499053
2025-08-27 20:32:42 +00:00
Jarrod Flesch
303381e049 fix(ui): prevent infinite redirect if no user (#13615)
Conditionally send the user to the inactivity route if there was a user
but refresh failed. You can get into an infinite loop if you call this
function externally and a redirect is being used in the url.
2025-08-27 19:43:14 +00:00
Jacob Fletcher
0a18306599 feat: configurable toast notifications (#13609)
Follow up to #12119.

You can now configure the toast notifications used in the admin panel
through the Payload config:

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

export default buildConfig({
  // ...
  admin: {
    // ...
    toast: {
      duration: 8000,
      limit: 1,
      // ...
    }
  }
})
```

_Note: the toast config is temporarily labeled as experimental to allow
for changes to the API, if necessary._

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211139422639711
2025-08-27 15:10:01 -04:00
Alessio Gravili
5ded64eaaf feat(db-*): support atomic array $push db updates (#13453)
This PR adds **atomic** `$push` **support for array fields**. It makes
it possible to safely append new items to arrays, which is especially
useful when running tasks in parallel (like job queues) where multiple
processes might update the same record at the same time. By handling
pushes atomically, we avoid race conditions and keep data consistent -
especially on postgres, where the current implementation would nuke the
entire array table before re-inserting every single array item.

The feature works for both localized and unlocalized arrays, and
supports pushing either single or multiple items at once.

This PR is a requirement for reliably running parallel tasks in the job
queue - see https://github.com/payloadcms/payload/pull/13452.

Alongside documenting `$push`, this PR also adds documentation for
`$inc`.

## Changes to updatedAt behavior

https://github.com/payloadcms/payload/pull/13335 allows us to override
the updatedAt property instead of the db always setting it to the
current date.

However, we are not able to skip updating the updatedAt property
completely. This means, usage of $push results in 2 postgres db calls:
1. set updatedAt in main row
2. append array row in arrays table

This PR changes the behavior to only automatically set updatedAt if it's
undefined. If you explicitly set it to `null`, this now allows you to
skip the db adapter automatically setting updatedAt.

=> This allows us to use $push in just one single db call

## Usage Examples

### Pushing a single item to an array

```ts
const post = (await payload.db.updateOne({
  data: {
    array: {
      $push: {
        text: 'some text 2',
        id: new mongoose.Types.ObjectId().toHexString(),
      },
    },
  },
  collection: 'posts',
  id: post.id,
}))
```

### Pushing a single item to a localized array

```ts
const post = (await payload.db.updateOne({
  data: {
    arrayLocalized: {
      $push: {
        en: {
          text: 'some text 2',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
        es: {
          text: 'some text 2 es',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
      },
    },
  },
  collection: 'posts',
  id: post.id,
}))
```

### Pushing multiple items to an array

```ts
const post = (await payload.db.updateOne({
  data: {
    array: {
      $push: [
        {
          text: 'some text 2',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
        {
          text: 'some text 3',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
      ],
    },
  },
  collection: 'posts',
  id: post.id,
}))
```

### Pushing multiple items to a localized array

```ts
const post = (await payload.db.updateOne({
  data: {
    arrayLocalized: {
      $push: {
        en: {
          text: 'some text 2',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
        es: [
          {
            text: 'some text 2 es',
            id: new mongoose.Types.ObjectId().toHexString(),
          },
          {
            text: 'some text 3 es',
            id: new mongoose.Types.ObjectId().toHexString(),
          },
        ],
      },
    },
  },
  collection: 'posts',
  id: post.id,
}))
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211110462564647
2025-08-27 14:11:08 -04:00
Jessica Rynkar
a67043854e fix: restore as draft function was passing a published status (#13599)
### What  
When using the `Restore as draft` action from the version view, the
restored document incorrectly appears as `published` in the API. This
issue was reported by a client.

### Why  
In the `restoreVersion` operation, the document is updated via
`db.updateOne` (line 265). The update passes along the status stored in
the version being restored but does not respect the `draft` query
parameter.

### How  
Ensures that the result status is explicitly set to `draft` when the
`draft` argument is `true`.
2025-08-27 14:09:26 -04:00
Sean Zubrickas
ded8ec4117 docs: adds default populate video (#13586)
Uses the VideoDrawer block to link relevant YouTube video at the bottom of defaultPopulate section
2025-08-27 10:58:04 -07:00
Elliot DeNolf
5c41966a32 ci: remove run-e2e-turbo flow (#13611)
Removing `run-e2e-turbo` flow as it was unused. Can re-introduce or make
this the default in future if needed.
2025-08-27 13:43:56 -04:00
Jarrod Flesch
6db07f0c03 feat(plugin-multi-tenant): allow custom tenant field per collection (#13553) 2025-08-27 13:31:20 -04:00
Alessio Gravili
13c24afa63 feat: allow multiple, different payload instances using getPayload in same process (#13603)
Fixes https://github.com/payloadcms/payload/issues/13433. Testing
release: `3.54.0-internal.90cf7d5`

Previously, when calling `getPayload`, you would always use the same,
cached payload instance within a single process, regardless of the
arguments passed to the `getPayload` function. This resulted in the
following issues - both are fixed by this PR:

- If, in your frontend you're calling `getPayload` without `cron: true`,
and you're hosting the Payload Admin Panel in the same process, crons
will not be enabled even if you visit the admin panel which calls
`getPayload` with `cron: true`. This will break jobs autorun depending
on which page you visit first - admin panel or frontend
- Within the same process, you are unable to use `getPayload` twice for
different instances of payload with different Payload Configs.
On postgres, you can get around this by manually calling new
`BasePayload()` which skips the cache. This did not work on mongoose
though, as mongoose was caching the models on a global singleton (this
PR addresses this).
In order to bust the cache for different Payload Config, this PR
introduces a new, optional `key` property to `getPayload`.


## Mongoose - disable using global singleton

This PR refactors the Payload Mongoose adapter to stop relying on the
global mongoose singleton. Instead, each adapter instance now creates
and manages its own scoped Connection object.

### Motivation

Previously, calling `getPayload()` more than once in the same process
would throw `Cannot overwrite model` errors because models were compiled
into the global singleton. This prevented running multiple Payload
instances side-by-side, even when pointing at different databases.

### Changes
- Replace usage of `mongoose.connect()` / `mongoose.model()` with
instance-scoped `createConnection()` and `connection.model()`.
- Ensure models, globals, and versions are compiled per connection, not
globally.
- Added proper `close()` handling on `this.connection` instead of
`mongoose.disconnect()`.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211114366468745
2025-08-27 10:24:37 -07:00
Jarrod Flesch
03a00ca37d fix(plugin-multi-tenant): prevent duplicate filters on referenced blocks (#13607)
Fixes https://github.com/payloadcms/payload/issues/13601

When using block references, tenant filter options were being applied
n+1 every time a block reference was used.
2025-08-27 12:41:57 -04:00
Jarrod Flesch
138938ec55 fix(ui): bulk edit overwriting fields within named tabs (#13600)
Fixes https://github.com/payloadcms/payload/issues/13429

Having a config like the following would remove data from the nested
tabs array field when bulk editing.


```ts
{
  type: 'tabs',
  tabs: [
    {
      label: 'Tabs Tabs Array',
      fields: [
        {
          type: 'tabs',
          tabs: [
            {
              name: 'tabTab',
              fields: [
                {
                  name: 'tabTabArray',
                  type: 'array',
                  fields: [
                    {
                      name: 'tabTabArrayText',
                      type: 'text',
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```
2025-08-26 16:44:57 -04:00
Patrik
1dc346af04 fix(graphql): invalid enum names when values include brackets (#13597)
### What?

Brackets (`[ ]`) in option values end up in GraphQL enum names via
`formatName`, causing schema generation to fail. This PR adds a single
rule to `formatName`:

- replace `[` and `]` with `_`

### Why?

Using `_` (instead of removing the brackets) is safer and more
consistent:

- Avoid collisions: removal can merge distinct strings (`"A[B]"` →
`"AB"`). `_` keeps them distinct (`"A_B"`).
- **Consistency**: `formatName` already maps punctuation to `_` (`. - /
+ , ( ) '`). Brackets follow the same rule.

Readability: `mb-[150px]` → `mb__150px_` is clearer than `mb150px`.

Digits/units safety: removal can jam characters (`w-[2/3]` → `w23`); `_`
avoids that (`w_2_3_`).

### How?

Update formatName to include a bracket replacement step:

```
.replace(/\[|\]/g, '_')
```

No other call sites or value semantics change; only names containing
brackets are affected.

Fixes #13466 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211141396953194
2025-08-26 11:03:26 -07:00
Jacob Fletcher
bd81936ad4 fix(ui): autosave in document drawer overwrites local changes (#13587)
Fixes #13574.

When editing an autosave-enabled document within a document drawer, any
changes made will while the autosave is processing are ultimately
discarded from local form state. This makes is difficult or even
impossible to edit fields.

This is because we server-render, then replace, the entire document on
every save. This includes form state, which is stale because it was
rendered while new changes were still being made. We don't need to
re-render the entire view on every save, though, only on create. We
don't do this on the top-level edit view, for example. Instead, we only
need to replace form state.

This change is also a performance improvement because we are no longer
rendering all components unnecessarily, especially on every autosave
interval.

Before:


https://github.com/user-attachments/assets/e9c221bf-4800-4153-af55-8b82e93b3c26

After:


https://github.com/user-attachments/assets/d77ef2f3-b98b-41d6-ba6c-b502b9bb99cc

Note: ignore the flashing autosave status and doc controls. This is
horrible and we're actively fixing it, but is outside the scope of this
PR.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211139422639700
2025-08-26 13:59:20 -04:00
Jarrod Flesch
cf37433667 fix(ui): multiple logout requests being made in parallel (#13595)
Fixes https://github.com/payloadcms/payload/issues/13565

The logout operation was running twice and causing a race condition on
user updates. This change ensures the logout operation only runs 1 time.

Really this view should have 1 purpose and that is to show the
inactivity view. Currently it has 2 purposes which is why it needs the
useEffect — the root of this issue. Instead we should just call the
`logOut` function from the logout button instead of it linking to a
logout page.
2025-08-26 13:54:57 -04:00
Patrik
344ad69572 fix(next): richtext fields not read-only for locked documents (#13579)
### What?

Ensure the Rich Text field is read-only when viewing a locked document.

### Why?

When opening a locked doc in read-only mode, the Rich Text field could
still appear editable. This is inconsistent with other fields and allows
users to try typing into a locked document.

### How?

- Pass `readOnly={isTrashedDoc || isLocked}` into `buildFormState` in
the edit view renderer.
2025-08-25 08:44:01 -07:00
Jarrod Flesch
f260d0ab49 feat(plugin-multi-tenant): re-enable global selector on all views (#13575)
Fixes #13559

Re-enable the global tenant selector on all views. In the last release
the global tenant filter was only enabled on tenant enabled collection
list views. This change allows the global tenant filter to be selected
on all non-document views. This is useful on custom views or custom
components on views that may not be tenant-enabled.
2025-08-25 11:29:55 -04:00
Sean Zubrickas
45c3be25b4 docs: blank-template-url-fix (#13580)
- encodes URL in Installation section so link renders properly
- Fixes #13403
2025-08-25 08:24:50 -07:00
Alessio Gravili
a92c251620 fix: hide jobs stats global by default (#13566)
The jobs stats global is used internally to get the scheduling system to
work. Currently, if scheduling is enabled, the "Payload Jobs Stats"
global is visible in the Payload Admin Panel:

<img width="524" height="252" alt="Screenshot 2025-08-23 at 00 10 13@2x"
src="https://github.com/user-attachments/assets/91702c93-4b58-4990-922b-8241ea9aa00e"
/>

Similarly to how we do it in the Payload Jobs collection, this PR hides
the payload jobs stats global from the admin panel by default

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211124797199081
2025-08-24 09:22:04 -04:00
Patrik
810184269d fix(ui): auth-fields container renders despite no visible auth/API key/verify content (#13554)
### What?

Prevents the Auth component from rendering an empty `.auth-fields`
wrapper.

### Why?

When `disableLocalStrategy` is true and `enableFields` is false, but
`useAPIKey` is true while
read access to API key fields is denied, the component still rendered
the parent wrapper with a
background—showing a blank box.

### How?

Introduce `hasVisibleContent`:

- `showAuthBlock = enableFields`
- `showAPIKeyBlock = useAPIKey && canReadApiKey`
- `showVerifyBlock = verify && isEditing`

If none are true, return `null`. (`disableLocalStrategy` is already
accounted for via `enableFields`.)

Fixes #12089 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211117270523574
2025-08-22 20:03:36 +00:00
Jacob Fletcher
1e13474068 fix: deeply merge array and block rows from server form state (#13551)
Continuation of https://github.com/payloadcms/payload/pull/13501.

When merging server form state with `acceptValues: true`, like on submit
(not autosave), rows are not deeply merged causing custom row
components, like row labels, to disappear. This is because we never
attach components to the form state response unless it has re-rendered
server-side, so unless we merge these rows with the current state, we
lose them.

Instead of allowing `acceptValues` to override all local changes to
rows, we need to flag any newly added rows with `addedByServer` so they
can bypass the merge strategy. Existing rows would continue to be merged
as expected, and new rows are simply appended to the end.

Discovered here:
https://discord.com/channels/967097582721572934/967097582721572937/1408367321797365840

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211115023863814
2025-08-22 15:41:51 -04:00
Rodrigo Solís
598ff4cf7b fix(ui): correctly pass query params to locked document requests (#10802) 2025-08-22 12:54:01 -04:00
Jarrod Flesch
409dd56f90 fix(plugin-multi-tenant): autosave global documents not rendering (#13552)
Fixes https://github.com/payloadcms/payload/issues/13507

When enabling autosave on global multi-tenant documents, the page would
not always render - more noticeably with smaller autosave intervals.
2025-08-22 12:47:13 -04:00
Alessio Gravili
5c16443431 fix: ensure updates to createdAt and updatedAt are respected (#13335)
Previously, when manually setting `createdAt` or `updatedAt` in a
`payload.db.*` or `payload.*` operation, the value may have been
ignored. In some cases it was _impossible_ to change the `updatedAt`
value, even when using direct db adapter calls. On top of that, this
behavior sometimes differed between db adapters. For example, mongodb
did accept `updatedAt` when calling `payload.db.updateVersion` -
postgres ignored it.

This PR changes this behavior to consistently respect `createdAt` and
`updatedAt` values for `payload.db.*` operations.

For `payload.*` operations, this also works with the following
exception:
- update operations do no respect `updatedAt`, as updates are commonly
performed by spreading the old data, e.g. `payload.update({ data:
{...oldData} })` - in these cases, we usually still want the `updatedAt`
to be updated. If you need to get around this, you can use the
`payload.db.updateOne` operation instead.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210919646303994
2025-08-22 08:41:07 -04:00
Logan Stellway
3408d7bdcd docs: fix small typo in documentation (#13545)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

-->

### What?

Fixes a tiny typo in the documentation. 

<!--

### Why?

### How?

Fixes #

-->

Co-authored-by: lstellway <lstellway@users.noreply.github.com>
2025-08-22 10:49:36 +01:00
Alessio Gravili
3bc1d0895f fix(richtext-lexical): toolbar dropdown items are disabled when selecting via double-click (#13544)
Fixes https://github.com/payloadcms/payload/issues/13275 by ensuring
that toolbar styles are updated on mount.

This PR also improves the lexical test suite by adding data attributes
that can be targeted more easily

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211110462564657
2025-08-22 09:44:55 +01:00
Yunsup Sim
ddb8ca4de2 fix(db-*): add delete version id for non-mongodb (#10613)
Fixes #10526 

### What?

`updateVersion` throws error at deleting before version of draft.

### Why?

When triggers `db.updateVersion` with non-mongodb environment,
`saveVersion` in `payload/src/versions/saveVersions.ts` doesn't remove
`id` in `versionData`.

### How?

Add `delete versionData.id` for non-mongodb environments.

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-08-22 09:39:21 +01:00
Vincent Vu
763cb61964 fix(next): update rest route handler types for Next.js 15.5 compatibility (#13521)
Fixes #13527.

When upgrading to [Next.js 15.5](https://nextjs.org/blog/next-15-5) with
Payload, you might experience a runtime or build error similar to this:

```ts
Type error: Type 'typeof import("/src/app/(payload)/api/graphql/route")' does not satisfy the expected type 'RouteHandlerConfig<"/api/graphql">'.
  Types of property 'OPTIONS' are incompatible.
    Type '(request: Request, args: { params: Promise<{ slug: string[]; }>; }) => Promise<Response>' is not assignable to type '(request: NextRequest, context: { params: Promise<{}>; }) => void | Promise<void> | Response | Promise<Response>'.
      Types of parameters 'args' and 'context' are incompatible.
        Type '{ params: Promise<{}>; }' is not assignable to type '{ params: Promise<{ slug: string[]; }>; }'.
          Types of property 'params' are incompatible.
            Type 'Promise<{}>' is not assignable to type 'Promise<{ slug: string[]; }>'.
              Property 'slug' is missing in type '{}' but required in type '{ slug: string[]; }'.
```

This is because Next.js route types are now _stricter_. Our REST handler
is nested within a catch-all `/api/[...slug]` route, so the slug param
_will_ exist in the handler—but the _same_ handler is re-used for the
`/api/graphql` OPTIONS route, which **_is not_** nested within the
`slug` param and so it **_will not_** exist as the types suggest.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211115021865680

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-08-21 16:38:03 -04:00
Elliot DeNolf
60cfc31f65 ci: ignore template bump commits in release notes [skip ci] 2025-08-21 16:12:03 -04:00
Elliot DeNolf
32069e2056 templates: bump for v3.53.0 (#13541)
🤖 Automated bump of templates for v3.53.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-08-21 16:02:50 -04:00
Elliot DeNolf
4151a902f2 chore(release): v3.53.0 [skip ci] 2025-08-21 14:27:59 -04:00
Jarrod Flesch
b65ca6832d fix(ui): thread id through instead of from routeParams (#13539)
Adjustment to https://github.com/payloadcms/payload/pull/13526

Prefer to thread ID through arguments instead of relying on routeParams
which would mean that ID is always a string - would not work for PG dbs
or non-text based ID's.
2025-08-21 18:14:58 +00:00
Jarrod Flesch
76741eb722 chore(plugin-multi-tenant): missing collections warning (#13538)
Fixes https://github.com/payloadcms/payload/issues/13517

⚠️ **Need to merge https://github.com/payloadcms/payload/pull/13379
first**

Adds warning if collections are enabled but not found. This can happen
if you add the multi-tenant before other plugins that add the
collections you are attempting to add multi-tenancy to.
2025-08-21 18:01:19 +00:00
Jónas G. Sigurðsson
2bdd669fde feat: add icelandic translations (#13423)
### What?

Add Icelandic translations

### Why?

It hadn't been implemented yet

### How?

I added files, mimicking the existing pattern for translations and
translated all messages in the list
2025-08-21 13:24:20 -04:00
Patrik
96074530b1 fix: server edit view components don't receive document id prop (#13526)
### What?

Make the document `id` available to server-rendered admin components
that expect it—specifically `EditMenuItems` and
`BeforeDocumentControls`—so `props.id` matches the official docs.

### Why?

The docs show examples using `props.id`, but the runtime `serverProps`
and TS types didn’t include it. This led to `undefined` at render time.

### How?

- Add id to ServerProps and set it in renderDocumentSlots from
req.routeParams.id.

Fixes #13420
2025-08-21 13:23:51 -04:00
Jarrod Flesch
5cf215d9cb feat(plugin-multi-tenant): visible tenant field on documents (#13379)
The goal of this PR is to show the selected tenant on the document level
instead of using the global selector to sync the state to the document.


Should merge https://github.com/payloadcms/payload/pull/13316 before
this one.

### Video of what this PR implements

**Would love feedback!**


https://github.com/user-attachments/assets/93ca3d2c-d479-4555-ab38-b77a5a9955e8
2025-08-21 13:15:24 -04:00
Alessio Gravili
393b4a0929 fix(next): no client field found error when accessing version view in some configurations (#13339)
This PR fixes some incorrect field paths handling (=> should not pass
path and schema paths that contain index paths down to sub-fields
outside of the indexPath property) when building the version fields.

Fixes https://github.com/payloadcms/payload/issues/12376

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210932060696925
2025-08-21 10:04:55 -04:00
Dan Ribbens
a94cd95b90 fix: uploads update unnecessarily resizing with sharp (#13528)
fixes https://github.com/payloadcms/payload/issues/13499
2025-08-21 09:24:21 -04:00
Patrik
a04bc9a3e7 fix(translations): stale version.versionCreatedOn key in translation (#13530)
### What?

Remove `versionCreatedOn` from translations.

### Why?

The key is no longer referenced anywhere in the admin/UI code.

Fixes #13188 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211105332940662
2025-08-20 15:20:40 -04:00
Anders Semb Hermansen
36fd6e905a perf(storage-s3): stream files and abort s3 request from static handler (#13430)
### What?

Stream S3 object directly to response instead of creating a Buffer in
memory and wire up an abort controller to stop streaming if user aborts
download

### Why?

To avoid excessive memory usage and to abort s3 download if user has
aborted the request anyway.

### How?

In node environment the AWS S3 always returns a Readable. The
streamToBuffer method always required this, but the any type hided that
this was actually needed. Now there is an explicit type check, but this
should never trigger in a node server environment.

Wire up and abort controller to the request so that we tell the S3
object to also stop streaming further if the user aborts.

Fixes #10286
Maybe also helps on other issues with s3 and resource usage
2025-08-20 14:53:21 -04:00
Jacob Fletcher
c67ceca8e2 perf(ui): do not fetch doc permissions on autosave (#13477)
No need to re-fetch doc permissions during autosave. This will save us
from making two additional client-side requests on every autosave
interval, on top of the two existing requests needed to autosave and
refresh form state.

This _does_ mean that the UI will not fully reflect permissions again
until you fully save, or until you navigating back, but that has always
been the behavior anyway (until #13416). Maybe we can find another
solution for this in the future, or otherwise consider this to be
expected behavior.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094073049052
2025-08-20 13:39:35 -04:00
Jacob Fletcher
f382c39dae fix: accept computed array and block rows from server form state (#13501)
If you have a beforeChange hook that manipulates arrays or blocks by
_adding rows_, the result of that hook will not be reflected in the UI
after save or autosave as you might expect.

For example, this hook that ensures at least one array row is populated:

```ts
{
  type: 'array',
  hooks: {
    beforeChange: [
      ({ value }) =>
        !value?.length
          ? [
              // this is an added/computed row if attempt to save with no rows
            ]
          : value,
    ],
  },
  // ...
}
```

When you save without any rows, this hook will have automatically
computed a row for you and saved it to the database. Form state will not
reflect this fact, however, until you refresh or navigate back.

This is for two reasons:
1. When merging server form state, we receive the new fields, but do not
receive the new rows. This is because the `acceptValues` flag only
applies to the `value` property of fields, but should also apply to the
`rows` property on `array` and `blocks` fields too.
2. When creating new form state on the server, the newly added rows are
not being flagged with `addedByServer`, and so never make it into form
state when it is merged in on the client. To do this we need to send the
previous form state to the server and set `renderAllFields` to false in
order receive this property as expected. Fixed by #13524.

Before:


https://github.com/user-attachments/assets/3ab07ef5-3afd-456f-a9a8-737909b75016

After:


https://github.com/user-attachments/assets/27ad1d83-9313-45a9-b44a-db1e64452a99

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094073049042
2025-08-20 13:39:25 -04:00
Said Akhrarov
fea6742ceb fix(plugin-form-builder): export radio field type (#11908)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
[#11716](https://github.com/payloadcms/payload/pull/11716) introduced
the `RadioField` component. This PR exports the type for use by
end-users.

### Why?
To allow end-users to utilize the type as expected with
`plugin-form-builder`.

### How?
Adds the `RadioField` interface to the list of exported plugin types.

Fixes #11806
2025-08-20 18:27:59 +01:00
Sasha
aa90271a59 fix(db-postgres): camelCase point fields (#13519)
Fixes https://github.com/payloadcms/payload/issues/13394
2025-08-20 20:18:14 +03:00
Jacob Fletcher
5e433aa9c3 perf(ui): reduce number of field renders on submit (#13524)
Needed for #13501.

No need to re-render all fields during save, and especially autosave.
Fields are already rendered. We only need to render new fields that may
have been created by hooks, etc.

We can achieve this by sending previous form state and new data through
the request with `renderAllFields: false`. That way form state can be
built up from previous form state, while still accepting new data.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094406108904
2025-08-20 12:39:48 -04:00
Gregor Billing
c7b9f0f563 fix(db-mongodb): disable join aggregations in DocumentDB compatibility mode (#13270)
### What?

The PR #12763 added significant improvements for third-party databases
that are compatible with the MongoDB API. While the original PR was
focused on Firestore, other databases like DocumentDB also benefit from
these compatibility features.

In particular, the aggregate JOIN strategy does not work on AWS
DocumentDB and thus needs to be disabled. The current PR aims to provide
this as a sensible default in the `compatibilityOptions` that are
provided by Payload out-of-the-box.

As a bonus, it also fixes a small typo from `compat(a)bility` to
`compat(i)bility`.

### Why?

Because our Payload instance, which is backed by AWS DocumentDB, crashes
upon trying to access any `join` field.

### How?

By adding the existing `useJoinAggregations` with value `false` to the
compatiblity layer. Individual developers can still choose to override
it in their own local config as needed.
2025-08-20 16:31:19 +00:00
Patrik
b3e48f8efa fix(ui): logout type error when user is null during locale switch (#13514)
### What?

Prevent a `TypeError: Cannot read properties of null (reading
'collection')` in the admin UI when switching locales by hardening
`AuthProvider.logOut`.

### Why?

During locale transitions, user can briefly be null. The existing code
used `user.collection` unguarded

### How?

- Use `userSlug` over `user.collection`.
- Always clear local auth in a `finally` block (`setNewUser(null)`,
`revokeTokenAndExpire()`), regardless of request outcome.

Fixes #13313 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211093549155962
2025-08-20 11:02:09 -04:00
Bob Bass
f44e27691e fix: 'front-end' spelling typo in JSDocs for overrideAccess (#13515)
### What?

Repeated typo in jsdoc documentation has been fixed

### Why?
'front-end' was reading as 'fron-end' in several jsdoc comments
2025-08-20 07:25:40 -07:00
Boyan Bratvanov
a840fc944b docs: fix typo in custom views (#13522)
A very small fix.
2025-08-20 13:40:57 +01:00
German Jablonski
cf427e5519 fix: imports (part 2/2) (#13520)
Completes https://github.com/payloadcms/payload/pull/13513

That PR fixed it for the `admin` suite, but I had also encountered the
same issue in `live-preview`.

When searching for instances, I found others with the same pattern
within packages, which I took the opportunity to fix in case the same
error occurs in the future.
2025-08-20 08:08:55 -04:00
Jacob Fletcher
adb83b1e06 test: add array field helpers (#13493)
Adds various helpers to make it easier and more standard to manage array
fields within e2e tests. Retrofits existing tests to ensure consistent
interactions across the board, and also organizes existing blocks and
relationship field helpers in the same way.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094073049046
2025-08-19 19:44:59 +00:00
Sasha
368cd901f8 fix(ui): replace deprecated document.execCommand('copy') API with navigator (#13431)
Replaces the deprecated API which might cause issues with the copy
button.

Also adds an E2E test for the copy button
2025-08-19 15:39:31 -04:00
Jarrod Flesch
406a09f4bf fix(ui): local image src changing when title changes (#13442)
When changing the title of a newly uploaded image, the image would
flicker because the `createObjectURL` was running and creating a new
local file url. This change allows `handleFileChange` to run and not
affect the url if the file being added is not a new file.

### Before


https://github.com/user-attachments/assets/9e21101e-c4cc-4fc3-b510-18f1a0d9fb3a



### After


https://github.com/user-attachments/assets/9f310e10-d29c-49a9-bd28-cb6da6c5651a
2025-08-19 15:39:03 -04:00
Patrik
4f6d0d8ed2 fix: select field component value prop type does not support array values (#13510)
### What?

Update `SelectFieldBaseClientProps` type so `value` accepts `string[]`
for `hasMany` selects

### Why?

Multi-selects currently error with “Type 'string[]' is not assignable to
type 'string'”.

### How?

- Change `value?: string` to `value?: string | string[]`
- Also adds additional multi select custom component to `admin` test
suite for testing

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-08-19 11:54:52 -07:00
Jarrod Flesch
9e7bb24ffb test: fix link imports (#13513)
Fixes issue in test suites that import the Link component from
next/link.
2025-08-19 14:51:11 -04:00
Jarrod Flesch
73ba4d1bb9 fix: unable to query versions on latest key (#13512)
Fixes https://github.com/payloadcms/payload/issues/13455

https://github.com/payloadcms/payload/pull/13297 Fixed a scoping issue,
but exposed a new issue where querying versioned documents by the
`latest` key would fail. This PR fixes the newly discoverable issue.
2025-08-19 11:42:02 -07:00
Patrik
332b2a9d3c fix(ui): double ? in gravatar url (#13511)
### What?

Ensure the Gravatar URL appends the query string only once.

### Why?

Previously `src` used `...?${query}` while `query` already began with
`?`, producing `??` and causing the avatar URL to be invalid in some
cases.

### How?

- Keep `query` as `?${params}` (from `URLSearchParams`).
- Change `src` from `https://www.gravatar.com/avatar/${hash}?${query}`
to `https://www.gravatar.com/avatar/${hash}${query}` so only one `?` is
present.

Fixes #13325
2025-08-19 11:03:53 -07:00
Sasha
92d459ec99 fix: avoid re-uploading the file unless changed (#13500)
Fixes https://github.com/payloadcms/payload/issues/13182

Before, the condition in
b714e6b151/packages/payload/src/uploads/generateFileData.ts#
was always passing. Now, with `shouldReupload` we properly check the
difference (whether the image was cropped or the focal point was
changed)
2025-08-19 17:50:50 +03:00
Jan Huenges
7699d02d7f fix(richtext-lexical): use thumbnail component for uploads (#12540)
### What?

Fix the broken thumbnail for non-images for uploads in the
richtext-lexical editor.

**Before:**

![before](https://github.com/user-attachments/assets/dbe5ffb7-9032-435b-8684-3c8bf53ae5bd)

**After:**

![after](https://github.com/user-attachments/assets/1c4af50e-2216-4ada-aff0-7a5e5559ac64)


### Why?

As described in #6867, the thumbnail in the richtext-lexical editor is
always trying to render an thumbnail image. This leads to a broken image
as soon as a non-image is added.

### How?

The fix was done by using the `<Thumbnail />` component from
`@payloadcms/ui`

Fixes #6867

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-08-19 14:14:51 +00:00
Паламар Роман
b714e6b151 fix(templates): plugin template correct paths for exports (#13427)
When using plugin template , initial package.json settings is wrong.
They point to non-existing files in exports folder ( index.ts ,
index.js, index.d.ts ) , which results in broken package if published
(can't import your plugin from package)

Fixes #13426
2025-08-19 14:10:18 +01:00
chenxi-debugger
379ef87d84 docs(db-mongodb): note on indexing localized fields & per-locale growth (#13469)
Closes #13464

Adds a note to the Indexes docs for localized fields:
- Indexing a `localized: true` field creates one index per locale path
(e.g. `slug.en`, `slug.da-dk`), which can grow index count on MongoDB.
- Recommends defining explicit indexes via collection-level `indexes`
for only the locale paths you actually query.
- Includes a concrete example (index `slug.en` only).

Docs-only change.

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-08-19 12:45:38 +00:00
Patrik
9f7d8c65d5 fix(ui): nested fields with admin.disableListColumn still appear as columns in list view (#13504)
### What?

This PR makes `filterFields` recurse into **fields with subfields**
(e.g., tabs, row, group, collapsible, array) so nested fields with
`admin.disableListColumn: true` (or hidden/disabled fields) are properly
excluded.

### Why?

Nested fields with `admin.disableListColumn: true` were still appearing
in the list view.

Example: a text field inside a `row` or `group` continued to show as a
column despite being marked `disableListColumn`.

### How?

- Call `filterFields` recursively for `tab.fields` and for any field
exposing a `fields` array.

Fixes #13496
2025-08-18 11:50:08 -07:00
Patrik
30ea8e1bac fix(ui): blocks field not respecting width styles in row layouts (#13502)
### What?

This PR applies `mergeFieldStyles` to the `BlocksField` component,
ensuring that custom admin styles such as `width` are correctly
respected when Blocks fields are placed inside row layouts.

### Why?

Previously, Blocks fields did not inherit or apply their `admin.width`
(or other merged field styles). For example, when placing two Blocks
fields side by side inside a row with `width: '50%'`, the widths were
ignored, causing layout issues.

### How?

- Imported and used `mergeFieldStyles` within `BlocksField`.
- Applied the merged styles to the root `<div>` via the `style` prop,
consistent with how other field components (like `TextField`) handle
styles.

Fixes #13498
2025-08-18 09:15:40 -07:00
German Jablonski
f9bbca8bfe docs: clarify pagination and improve cross-referencing (#13503)
Fixes #13417

Builds on #13471

---------

Co-authored-by: chenxi-debugger <chenxi.debugger@gmail.com>
2025-08-18 16:55:52 +01:00
id3er0
9d08f503ae fix(storage-s3): validate Content-Length before appending header (#13472)
## Description
Fixes "Parse Error: Invalid character in Content-Length" errors that
occur when S3-compatible storage providers (like MinIO) return undefined
or invalid ContentLength values.

## Changes
- Added validation before appending Content-Length header in
`staticHandler.ts`
- Only appends Content-Length when value is present and numeric
- Prevents HTTP specification violations from undefined/invalid values

## Code Changes
```typescript
const contentLength = String(object.ContentLength);
if (contentLength && !isNaN(Number(contentLength))) {
  headers.append('Content-Length', contentLength);
}
```

## Issue
- Resolves MinIO compatibility issues where undefined ContentLength
causes client parse errors
- Maintains backward compatibility when ContentLength is valid

## Testing
- [x] Tested with MinIO provider returning undefined ContentLength
- [x] Verified valid Content-Length values are still properly set
- [x] Confirmed no regression in existing S3 functionality

### Type of Change
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)

### Checklist
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings
- [x] Any dependent changes have been merged and published
```
2025-08-15 19:50:27 +00:00
Patrik
a7ed88b5fa feat(plugin-import-export): use groupBy as SortBy when present and sort is unset (#13491)
### What?

When exporting, if no `sort` parameter is set but a `groupBy` parameter
is present in the list-view query, the export will treat `groupBy` as
the SortBy field and default to ascending order.
Additionally, the SortOrder field in the export UI is now hidden when no
sort is present, reducing visual noise and preventing irrelevant order
selection.

### Why?

Previously, exports ignored `groupBy` entirely when no sort was set,
leading to unsorted output even if the list view was grouped. Also,
SortOrder was always shown, even when no sort field was selected, which
could be confusing. These changes ensure exports reflect the list view’s
grouping and keep the UI focused.

### How?

- Check for `groupBy` in the query only when `sort` is unset.
- If found, set SortBy to `groupBy` and SortOrder to ascending.
- Hide the SortOrder field when `sort` is not set.
- Leave sorting unset if neither `sort` nor `groupBy` are present.
2025-08-15 11:56:58 -07:00
Sasha
ec5b673aca fix: copy to locale with localized fields inside tabs (#13456)
Fixes https://github.com/payloadcms/payload/issues/13374
2025-08-15 14:55:12 -04:00
Sasha
3dd142c637 fix(ui): cannot replace the file if the user does not have delete access (#13484)
Currently, if you don't have delete access to the document, the UI
doesn't allow you to replace the file, which isn't expected. This is
also a UI only restriction, and the API allows you do this fine.

This PR makes so the "remove file" button renders even if you don't have
delete access, while still ensures you have update access.

---------

Co-authored-by: Paul Popus <paul@payloadcms.com>
2025-08-15 14:52:45 -04:00
Jarrod Flesch
1909063e42 fix: omit trashed documents from appearing in folder results (#13492)
### Issue

The folders join field query was returning trashed documents. 

### Fix
Adds a constraint to trash enabled collections, which prevents trashed
documents from being surfaced in folder views.
2025-08-15 14:45:36 -04:00
Anatoly Kopyl
64f4b0aff3 fix: update docker base image in templates (#13020)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->

Went ahead and bumped the base from `node:22.12.0-alpine` to
`node:22.17.0-alpine` across all templates to fix #13019.
2025-08-15 18:49:02 +01:00
Said Akhrarov
c8ef92449b fix(next): add missing translations to version view
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR translates the block and item label in the version view, as well
as the version label in the step nav.

### Why?
To appropriately translate labels in the version views.

### How?
By using the TFunction from useTranslation as well as existing
translation keys.

Fixes #13193
Fixes #13194
2025-08-15 18:43:01 +01:00
Patrik
b7243b1413 fix(ui): bulk upload action bar buttons wrapping (#13486)
### What?

Added `white-space: nowrap` to the `.bulk-upload--actions-bar__buttons`
class to ensure button labels remain on a single line.

### Why?

In the bulk upload action bar, buttons containing multi-word labels were
wrapping to two lines, causing them to expand vertically and misalign
with other controls.

### How?

Applied `white-space: nowrap` to `.bulk-upload--actions-bar__buttons` so
all button labels stay on one line, maintaining consistent height and
alignment.

#### Before
<img width="1469" height="525" alt="Screenshot 2025-08-15 at 9 20 07 AM"
src="https://github.com/user-attachments/assets/aecc65ae-7b2f-43ba-96c8-1143fcee7f88"
/>

#### After
<img width="1474" height="513" alt="Screenshot 2025-08-15 at 9 19 55 AM"
src="https://github.com/user-attachments/assets/438c6ee1-b966-4966-8686-37ba4619a25c"
/>
2025-08-15 11:49:08 -04:00
Elliot DeNolf
f5d77662b0 templates: bump for v3.52.0 (#13488)
🤖 Automated bump of templates for v3.52.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-08-15 11:28:20 -04:00
Patrik
efdf00200a feat(plugin-import-export): adds sort order control and sync with sort-by field (#13478)
### What?

This PR adds a dedicated `sortOrder` select field (Ascending /
Descending) to the import-export plugin, alongside updates to the
existing `SortBy` component. The new field and component logic keep the
sort field (`sort`) in sync with the selected sort direction.

### Why?

Previously, descending sorting did not work. While the `SortBy` field
could read the list view’s `query.sort` param, if the value contained a
leading dash (e.g. `-title`), it would not be handled correctly. Only
ascending sorts such as `sort=title` worked, and the preview table would
not reflect a descending order.

### How?

- Added a new `sortOrder` select field to the export options schema.
- Implemented a `SortOrder` custom component using ReactSelect:
- On new exports, reads `query.sort` from the list view and sets both
`sortOrder` and `sort` (combined value with or without a leading dash).
  - Handles external changes to `sort` that include a leading dash.
- Updated `SortBy`:
- No longer writes to `sort` during initial hydration—`SortOrder` owns
initial value setting.
- On user field changes, writes the combined value using the current
`sortOrder`.
2025-08-15 11:27:54 -04:00
Elliot DeNolf
217606ac20 chore(release): v3.52.0 [skip ci] 2025-08-15 10:42:32 -04:00
jacobsfletch
0b60bf2eff fix(ui): significantly more predictable autosave form state (#13460) 2025-08-14 19:36:02 -04:00
Sasha
46699ec314 test: skip cookies filter for internal URLs in getExternalFile (#13476)
Test for https://github.com/payloadcms/payload/pull/13475
2025-08-14 13:17:17 -04:00
Sean Zubrickas
cdd90f91c8 docs: updates image paths to new screenshots (#13461)
Updates images paths for the following screenshots:

- auth-overview.jpg
- autosave-drafts.jpg
- autosave-v3.jpg
- uploads-overview.jpg
- versions-v3.jpg
2025-08-14 13:15:35 -04:00
Sasha
8d4e7f5f30 fix: filter payload- cookies in getExternalFile only if the URL is external (#13475)
Fixes a regression from
https://github.com/payloadcms/payload/pull/13215. Fixes the issue when
`skipSafeFetch: true` is set
https://github.com/payloadcms/payload/issues/13146#issuecomment-3066858749

This PR makes it so we still send the cookies if we do `fetch` to our
server, but filter them when we `fetch` to an external server (usually a
third party storage, for which we don't want to expose those cookies)
2025-08-14 14:51:24 +00:00
jacobsfletch
b426052cab test: fix import-export plugin int (#13474)
CI is blocked because of failing int tests within the import/export
plugin suite after #13380.
2025-08-14 08:54:13 -04:00
Sasha
047519f47f fix(db-postgres): ensure index names are not too long (#13428)
Fixes https://github.com/payloadcms/payload/issues/13196
2025-08-14 02:44:56 +03:00
Dan Ribbens
c1c68fbb55 feat(plugin-import-export): adds limit and page fields to export options (#13380)
### What:
This PR adds `limit` and `page` fields to the export options, allowing
users to control the number of documents exported and the page from
which to start the export. It also enforces that limit must be a
positive multiple of 100.

### Why:
This feature is needed to provide pagination support for large exports,
enabling users to export manageable chunks of data rather than the
entire dataset at once. Enforcing multiples-of-100 for `limit` ensures
consistent chunking behavior and prevents unexpected export issues.

### How:
- The `limit` field determines the maximum number of documents to export
and **must be a positive multiple of 100**.
- The `page` field defines the starting page of the export and is
displayed only when a `limit` is specified.
- If `limit` is cleared, the `page` resets to 1 to maintain consistency.
- Export logic was adjusted to respect the `limit` and `page` values
when fetching documents.

---------

Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
2025-08-13 14:01:45 -07:00
Patrik
3e65111bc1 fix(plugin-import-export): csv export & preview showing full documents for hasMany monomorphic relationships instead of just ID (#13465)
### What?

Fixes an issue where CSV exports and the preview table displayed all
fields of documents in hasMany monomorphic relationships instead of only
their IDs.

### Why?

This caused cluttered output and inconsistent CSV formats, since only
IDs should be exported for hasMany monomorphic relationships.

### How?

Added explicit `toCSV` handling for all relationship types in
`getCustomFieldFunctions`, updated `flattenObject` to delegate to these
handlers, and adjusted `getFlattenedFieldKeys` to generate the correct
headers.
2025-08-13 13:54:32 -07:00
Elliot DeNolf
0e8a6c0162 templates: bump for v3.51.0 (#13457)
🤖 Automated bump of templates for v3.51.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-08-13 09:47:04 -04:00
613 changed files with 11149 additions and 4244 deletions

View File

@@ -26,7 +26,7 @@ runs:
steps:
- name: Checkout code
if: ${{ github.event_name != 'pull_request' }}
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Run action
run: node ${{ github.action_path }}/dist/index.js
shell: sh

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup
uses: ./.github/actions/setup
@@ -34,7 +34,7 @@ jobs:
- name: Slack notification on failure
if: failure()
uses: slackapi/slack-github-action@v2.1.0
uses: slackapi/slack-github-action@v2.1.1
with:
webhook: ${{ inputs.debug == 'true' && secrets.SLACK_TEST_WEBHOOK_URL || secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook

View File

@@ -11,7 +11,7 @@ jobs:
name: Repository dispatch
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Dispatch event
if: ${{ github.event_name == 'workflow_dispatch' }}

View File

@@ -6,7 +6,6 @@ on:
- opened
- reopened
- synchronize
- labeled
push:
branches:
- main
@@ -34,7 +33,7 @@ jobs:
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: filter
with:
@@ -63,7 +62,7 @@ jobs:
lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -79,7 +78,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
@@ -99,7 +98,7 @@ jobs:
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
@@ -123,7 +122,7 @@ jobs:
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
@@ -185,7 +184,7 @@ jobs:
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
@@ -310,7 +309,7 @@ jobs:
env:
SUITE_NAME: ${{ matrix.suite }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
@@ -371,6 +370,7 @@ jobs:
# report-tag: ${{ matrix.suite }}
# job-summary: true
# This is unused, keeping it here for reference and possibly enabling in the future
tests-e2e-turbo:
runs-on: ubuntu-24.04
needs: [changes, build]
@@ -447,7 +447,7 @@ jobs:
env:
SUITE_NAME: ${{ matrix.suite }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
@@ -550,7 +550,7 @@ jobs:
MONGODB_VERSION: 6.0
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
@@ -647,7 +647,7 @@ jobs:
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
@@ -706,7 +706,7 @@ jobs:
actions: read # for fetching base branch bundle stats
pull-requests: write # for comments
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup

View File

@@ -17,7 +17,7 @@ jobs:
release_tag: ${{ steps.determine_tag.outputs.release_tag }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
sparse-checkout: .github/workflows
@@ -54,7 +54,7 @@ jobs:
POSTGRES_DB: payloadtests
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup
uses: ./.github/actions/setup

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-24.04
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: ./.github/actions/release-commenter
continue-on-error: true
env:
@@ -43,9 +43,9 @@ jobs:
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Github Releases To Discord
uses: SethCohen/github-releases-to-discord@v1.16.2
uses: SethCohen/github-releases-to-discord@v1.19.0
with:
webhook_url: ${{ secrets.DISCORD_RELEASES_WEBHOOK_URL }}
color: '16777215'

View File

@@ -14,7 +14,7 @@ jobs:
name: lint-pr-title
runs-on: ubuntu-24.04
steps:
- uses: amannn/action-semantic-pull-request@v5
- uses: amannn/action-semantic-pull-request@v6
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup
uses: ./.github/actions/setup
- name: Load npm token

View File

@@ -90,7 +90,7 @@ jobs:
if: github.event_name == 'issues'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.base.ref }}
token: ${{ secrets.GITHUB_TOKEN }}

2
.gitignore vendored
View File

@@ -331,5 +331,7 @@ test/databaseAdapter.js
test/.localstack
test/google-cloud-storage
test/azurestoragedata/
/media-without-delete-access
licenses.csv

54
AGENTS.md Normal file
View File

@@ -0,0 +1,54 @@
# Payload Monorepo Agent Instructions
## Project Structure
- Packages are located in the `packages/` directory.
- The main Payload package is `packages/payload`. This contains the core functionality.
- Database adapters are in `packages/db-*`.
- The UI package is `packages/ui`.
- The Next.js integration is in `packages/next`.
- Rich text editor packages are in `packages/richtext-*`.
- Storage adapters are in `packages/storage-*`.
- Email adapters are in `packages/email-*`.
- Plugins which add additional functionality are in `packages/plugin-*`.
- Documentation is in the `docs/` directory.
- Monorepo tooling is in the `tools/` directory.
- Test suites and configs are in the `test/` directory.
- LLMS.txt is at URL: https://payloadcms.com/llms.txt
- LLMS-FULL.txt is at URL: https://payloadcms.com/llms-full.txt
## Dev environment tips
- Any package can be built using a `pnpm build:*` script defined in the root `package.json`. These typically follow the format `pnpm build:<directory_name>`. The options are all of the top-level directories inside the `packages/` directory. Ex `pnpm build:db-mongodb` which builds the `packages/db-mongodb` package.
- ALL packages can be built with `pnpm build:all`.
- Use `pnpm dev` to start the monorepo dev server. This loads the default config located at `test/_community/config.ts`.
- Specific dev configs for each package can be run with `pnpm dev <directory_name>`. The options are all of the top-level directories inside the `test/` directory. Ex `pnpm dev fields` which loads the `test/fields/config.ts` config. The directory name can either encompass a single area of functionality or be the name of a specific package.
## Testing instructions
- There are unit, integration, and e2e tests in the monorepo.
- Unit tests can be run with `pnpm test:unit`.
- Integration tests can be run with `pnpm test:int`. Individual test suites can be run with `pnpm test:int <directory_name>`, which will point at `test/<directory_name>/int.spec.ts`.
- E2E tests can be run with `pnpm test:e2e`.
- All tests can be run with `pnpm test`.
- Prefer running `pnpm test:int` for verifying local code changes.
## PR Guidelines
- This repository follows conventional commits for PR titles
- PR Title format: <type>(<scope>): <title>. Title must start with a lowercase letter.
- Valid types are build, chore, ci, docs, examples, feat, fix, perf, refactor, revert, style, templates, test
- Prefer `feat` for new features and `fix` for bug fixes.
- Valid scopes are the following regex patterns: cpa, db-\*, db-mongodb, db-postgres, db-vercel-postgres, db-sqlite, drizzle, email-\*, email-nodemailer, email-resend, eslint, graphql, live-preview, live-preview-react, next, payload-cloud, plugin-cloud, plugin-cloud-storage, plugin-form-builder, plugin-import-export, plugin-multi-tenant, plugin-nested-docs, plugin-redirects, plugin-search, plugin-sentry, plugin-seo, plugin-stripe, richtext-\*, richtext-lexical, richtext-slate, storage-\*, storage-azure, storage-gcs, storage-uploadthing, storage-vercel-blob, storage-s3, translations, ui, templates, examples(\/(\w|-)+)?, deps
- Scopes should be chosen based upon the package(s) being modified. If multiple packages are being modified, choose the most relevant one or no scope at all.
- Example PR titles:
- `feat(db-mongodb): add support for transactions`
- `feat(richtext-lexical): add options to hide block handles`
- `fix(ui): json field type ignoring editorOptions`
## Commit Guidelines
- This repository follows conventional commits for commit messages
- The first commit of a branch should follow the PR title format: <type>(<scope>): <title>. Follow the same rules as PR titles.
- Subsequent commits should prefer `chore` commits without a scope unless a specific package is being modified.
- These will eventually be squashed into the first commit when merging the PR.

1
CLAUDE.md Symbolic link
View File

@@ -0,0 +1 @@
AGENTS.md

View File

@@ -98,6 +98,7 @@ The following options are available:
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| `autoLogin` | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| `autoRefresh` | Used to automatically refresh user tokens for users logged into the dashboard. [More details](../authentication/overview). |
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](../custom-components/overview). |
| `custom` | Any custom properties you wish to pass to the Admin Panel. |
| `dateFormat` | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
@@ -107,6 +108,7 @@ The following options are available:
| `suppressHydrationWarning` | If set to `true`, suppresses React hydration mismatch warnings during the hydration of the root `<html>` tag. Defaults to `false`. |
| `theme` | Restrict the Admin Panel theme to use only one of your choice. Default is `all`. |
| `timezones` | Configure the timezone settings for the admin panel. [More details](#timezones) |
| `toast` | Customize the handling of toast messages within the Admin Panel. [More details](#toasts) |
| `user` | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
<Banner type="success">
@@ -298,3 +300,20 @@ We validate the supported timezones array by checking the value against the list
`timezone: true`. See [Date Fields](../fields/overview#date) for more
information.
</Banner>
## Toast
The `admin.toast` configuration allows you to customize the handling of toast messages within the Admin Panel, such as increasing the duration they are displayed and limiting the number of visible toasts at once.
<Banner type="info">
**Note:** The Admin Panel currently uses the
[Sonner](https://sonner.emilkowal.ski) library for toast notifications.
</Banner>
The following options are available for the `admin.toast` configuration:
| Option | Description | Default |
| ---------- | ---------------------------------------------------------------------------------------------------------------- | ------- |
| `duration` | The length of time (in milliseconds) that a toast message is displayed. | `4000` |
| `expand` | If `true`, will expand the message stack so that all messages are shown simultaneously without user interaction. | `false` |
| `limit` | The maximum number of toasts that can be visible on the screen at once. | `5` |

View File

@@ -49,7 +49,7 @@ export const Users: CollectionConfig = {
strategies: [
{
name: 'custom-strategy',
authenticate: ({ payload, headers }) => {
authenticate: async ({ payload, headers }) => {
const usersQuery = await payload.find({
collection: 'users',
where: {
@@ -66,7 +66,7 @@ export const Users: CollectionConfig = {
// Send the user with the collection slug back to authenticate,
// or send null if no user should be authenticated
user: usersQuery.docs[0] ? {
collection: 'users'
collection: 'users',
...usersQuery.docs[0],
} : null,

View File

@@ -33,7 +33,7 @@ export const Users: CollectionConfig = {
}
```
![Authentication Admin Panel functionality](https://payloadcms.com/images/docs/auth-admin.jpg)
![Authentication Admin Panel functionality](https://payloadcms.com/images/docs/auth-overview.jpg)
_Admin Panel screenshot depicting an Admins Collection with Auth enabled_
## Config Options
@@ -173,6 +173,25 @@ The following options are available:
| **`password`** | The password of the user to login as. This is only needed if `prefillOnly` is set to true |
| **`prefillOnly`** | If set to true, the login credentials will be prefilled but the user will still need to click the login button. |
## Auto-Refresh
Turning this property on will allow users to stay logged in indefinitely while their browser is open and on the admin panel, by automatically refreshing their authentication token before it expires.
To enable auto-refresh for user tokens, set `autoRefresh: true` in the [Payload Config](../admin/overview#admin-options) to:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
admin: {
autoRefresh: true,
},
// highlight-end
})
```
## Operations
All auth-related operations are available via Payload's REST, Local, and GraphQL APIs. These operations are automatically added to your Collection when you enable Authentication. [More details](./operations).

View File

@@ -141,7 +141,7 @@ The following options are available:
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
| `pagination` | Set pagination-specific options for this Collection in the List View. [More details](#pagination). |
| `baseFilter` | Defines a default base filter which will be applied to the List View (along with any other filters applied by the user) and internal links in Lexical Editor, |
<Banner type="warning">

View File

@@ -131,6 +131,29 @@ localization: {
Since the filtering happens at the root level of the application and its result is not calculated every time you navigate to a new page, you may want to call `router.refresh` in a custom component that watches when values that affect the result change. In the example above, you would want to do this when `supportedLocales` changes on the tenant document.
## Experimental Options
Experimental options are features that may not be fully stable and may change or be removed in future releases.
These options can be enabled in your Payload Config under the `experimental` key. You can set them like this:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
experimental: {
localizeStatus: true,
},
})
```
The following experimental options are available related to localization:
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`localizeStatus`** | **Boolean.** When `true`, shows document status per locale in the admin panel instead of always showing the latest overall status. Opt-in for backwards compatibility. Defaults to `false`. |
## Field Localization
Payload Localization works on a **field** level—not a document level. In addition to configuring the base Payload Config to support Localization, you need to specify each field that you would like to localize.

View File

@@ -70,6 +70,7 @@ The following options are available:
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
| **`bin`** | Register custom bin scripts for Payload to execute. [More Details](#custom-bin-scripts). |
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
| **`experimental`** | Configure experimental features for Payload. These may be unstable and may change or be removed in future releases. [More details](../experimental). |
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |

View File

@@ -158,7 +158,7 @@ export function MyCustomView(props: AdminViewServerProps) {
<Banner type="success">
**Tip:** For consistent layout and navigation, you may want to wrap your
Custom View with one of the built-in [Template](./overview#templates).
Custom View with one of the built-in [Templates](./overview#templates).
</Banner>
### View Templates

View File

@@ -293,7 +293,6 @@ Here's an example of a custom `editMenuItems` component:
```tsx
import React from 'react'
import { PopupList } from '@payloadcms/ui'
import type { EditMenuItemsServerProps } from 'payload'
@@ -301,12 +300,12 @@ export const EditMenuItems = async (props: EditMenuItemsServerProps) => {
const href = `/custom-action?id=${props.id}`
return (
<PopupList.ButtonGroup>
<PopupList.Button href={href}>Custom Edit Menu Item</PopupList.Button>
<PopupList.Button href={href}>
<>
<a href={href}>Custom Edit Menu Item</a>
<a href={href}>
Another Custom Edit Menu Item - add as many as you need!
</PopupList.Button>
</PopupList.ButtonGroup>
</a>
</>
)
}
```

View File

@@ -63,3 +63,22 @@ export const MyCollection: CollectionConfig = {
],
}
```
## Localized fields and MongoDB indexes
When you set `index: true` or `unique: true` on a localized field, MongoDB creates one index **per locale path** (e.g., `slug.en`, `slug.da-dk`, etc.). With many locales and indexed fields, this can quickly approach MongoDB's per-collection index limit.
If you know you'll query specifically by a locale, index only those locale paths using the collection-level `indexes` option instead of setting `index: true` on the localized field. This approach gives you more control and helps avoid unnecessary indexes.
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
fields: [{ name: 'slug', type: 'text', localized: true }],
indexes: [
// Index English slug only (rather than all locales)
{ fields: ['slug.en'] },
// You could also make it unique:
// { fields: ['slug.en'], unique: true },
],
}
```

View File

@@ -60,21 +60,21 @@ You can access Mongoose models as follows:
## Using other MongoDB implementations
You can import the `compatabilityOptions` object to get the recommended settings for other MongoDB implementations. Since these databases aren't officially supported by payload, you may still encounter issues even with these settings (please create an issue or PR if you believe these options should be updated):
You can import the `compatibilityOptions` object to get the recommended settings for other MongoDB implementations. Since these databases aren't officially supported by payload, you may still encounter issues even with these settings (please create an issue or PR if you believe these options should be updated):
```ts
import { mongooseAdapter, compatabilityOptions } from '@payloadcms/db-mongodb'
import { mongooseAdapter, compatibilityOptions } from '@payloadcms/db-mongodb'
export default buildConfig({
db: mongooseAdapter({
url: process.env.DATABASE_URI,
// For example, if you're using firestore:
...compatabilityOptions.firestore,
...compatibilityOptions.firestore,
}),
})
```
We export compatability options for [DocumentDB](https://aws.amazon.com/documentdb/), [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db) and [Firestore](https://cloud.google.com/firestore/mongodb-compatibility/docs/overview). Known limitations:
We export compatibility options for [DocumentDB](https://aws.amazon.com/documentdb/), [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db) and [Firestore](https://cloud.google.com/firestore/mongodb-compatibility/docs/overview). Known limitations:
- Azure Cosmos DB does not support transactions that update two or more documents in different collections, which is a common case when using Payload (via hooks).
- Azure Cosmos DB the root config property `indexSortableFields` must be set to `true`.

View File

@@ -0,0 +1,66 @@
---
title: Experimental Features
label: Overview
order: 10
desc: Enable and configure experimental functionality within Payload. These featuresmay be unstable and may change or be removed without notice.
keywords: experimental, unstable, beta, preview, features, configuration, Payload, cms, headless, javascript, node, react, nextjs
---
Experimental features allow you to try out new functionality before it becomes a stable part of Payload. These features may still be in active development, may have incomplete functionality, and can change or be removed in future releases without warning.
## How It Works
Experimental features are configured via the root-level `experimental` property in your [Payload Config](../configuration/overview). This property contains individual feature flags, each flag can be configured independently, allowing you to selectively opt into specific functionality.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
experimental: {
localizeStatus: true, // highlight-line
},
})
```
## Experimental Options
The following options are available:
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`localizeStatus`** | **Boolean.** When `true`, shows document status per locale in the admin panel instead of always showing the latest overall status. Opt-in for backwards compatibility. Defaults to `false`. |
This list may change without notice.
## When to Use Experimental Features
You might enable an experimental feature when:
- You want early access to new capabilities before their stable release.
- You can accept the risks of using potentially unstable functionality.
- You are testing new features in a development or staging environment.
- You wish to provide feedback to the Payload team on new functionality.
If you are working on a production application, carefully evaluate whether the benefits outweigh the risks. For most stable applications, it is recommended to wait until the feature is officially released.
<Banner type="success">
<strong>Tip:</strong> To stay up to date on experimental features or share
your feedback, visit the{' '}
<a
href="https://github.com/payloadcms/payload/discussions"
target="_blank"
rel="noopener noreferrer"
>
Payload GitHub Discussions
</a>{' '}
or{' '}
<a
href="https://github.com/payloadcms/payload/issues"
target="_blank"
rel="noopener noreferrer"
>
open an issue
</a>
.
</Banner>

View File

@@ -81,7 +81,7 @@ To install a Database Adapter, you can run **one** of the following commands:
#### 2. Copy Payload files into your Next.js app folder
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/%28payload%29) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
```plaintext
app/

View File

@@ -162,6 +162,11 @@ const result = await payload.find({
})
```
<Banner type="info">
`pagination`, `page`, and `limit` are three related properties [documented
here](/docs/queries/pagination).
</Banner>
### Find by ID#collection-find-by-id
```js

View File

@@ -207,7 +207,7 @@ Everything mentioned above applies to local development as well, but there are a
### Enable Turbopack
<Banner type="warning">
**Note:** In the future this will be the default. Use as your own risk.
**Note:** In the future this will be the default. Use at your own risk.
</Banner>
Add `--turbo` to your dev script to significantly speed up your local development server start time.

View File

@@ -79,6 +79,7 @@ formBuilderPlugin({
text: true,
textarea: true,
select: true,
radio: true,
email: true,
state: true,
country: true,
@@ -293,14 +294,46 @@ Maps to a `textarea` input on your front-end. Used to collect a multi-line strin
Maps to a `select` input on your front-end. Used to display a list of options.
| Property | Type | Description |
| -------------- | -------- | -------------------------------------------------------- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
| `options` | array | An array of objects with `label` and `value` properties. |
| Property | Type | Description |
| -------------- | -------- | ------------------------------------------------------------------------------- |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `placeholder` | string | The placeholder text for the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
| `options` | array | An array of objects that define the select options. See below for more details. |
#### Select Options
Each option in the `options` array defines a selectable choice for the select field.
| Property | Type | Description |
| -------- | ------ | ----------------------------------- |
| `label` | string | The display text for the option. |
| `value` | string | The value submitted for the option. |
### Radio
Maps to radio button inputs on your front-end. Used to allow users to select a single option from a list of choices.
| Property | Type | Description |
| -------------- | -------- | ------------------------------------------------------------------------------ |
| `name` | string | The name of the field. |
| `label` | string | The label of the field. |
| `defaultValue` | string | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
| `options` | array | An array of objects that define the radio options. See below for more details. |
#### Radio Options
Each option in the `options` array defines a selectable choice for the radio field.
| Property | Type | Description |
| -------- | ------ | ----------------------------------- |
| `label` | string | The display text for the option. |
| `value` | string | The value submitted for the option. |
### Email (field)

View File

@@ -80,6 +80,11 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
* @default false
*/
isGlobal?: boolean
/**
* Opt out of adding the tenant field and place
* it manually using the `tenantField` export from the plugin
*/
customTenantField?: boolean
/**
* Overrides for the tenant field, will override the entire tenantField configuration
*/

View File

@@ -148,6 +148,12 @@ export const Pages: CollectionConfig<'pages'> = {
}
```
<VideoDrawer
id="Snqjng_w-QU"
label="Watch default populate in action"
drawerTitle="How to easily optimize Payload CMS requests with defaultPopulate"
/>
<Banner type="warning">
**Important:** When using `defaultPopulate` on a collection with
[Uploads](/docs/fields/upload) enabled and you want to select the `url` field,

View File

@@ -773,3 +773,28 @@ const res = await fetch(`${api}/${collectionSlug}?depth=1&locale=en`, {
},
})
```
### Passing as JSON
When using `X-Payload-HTTP-Method-Override`, it expects the body to be a query string. If you want to pass JSON instead, you can set the `Content-Type` to `application/json` and include the JSON body in the request.
#### Example
```ts
const res = await fetch(`${api}/${collectionSlug}/${id}`, {
// Only the findByID endpoint supports HTTP method overrides with JSON data
method: 'POST',
credentials: 'include',
headers: {
'Accept-Language': i18n.language,
'Content-Type': 'application/json',
'X-Payload-HTTP-Method-Override': 'GET',
},
body: JSON.stringify({
depth: 1,
locale: 'en',
}),
})
```
This can be more efficient for large JSON payloads, as you avoid converting data to and from query strings. However, only certain endpoints support this. Supported endpoints will read the parsed body under a `data` property, instead of reading from query parameters as with standard GET requests.

View File

@@ -11,7 +11,7 @@ keywords: lexical, richtext, html
There are two main approaches to convert your Lexical-based rich text to HTML:
1. **Generate HTML on-demand (Recommended)**: Convert JSON to HTML wherever you need it, on-demand.
2. **Generate HTML within your Collection**: Create a new field that automatically converts your saved JSON content to HTML. This is not recommended because it adds overhead to the Payload API and may not work well with live preview.
2. **Generate HTML within your Collection**: Create a new field that automatically converts your saved JSON content to HTML. This is not recommended because it adds overhead to the Payload API.
### On-demand
@@ -101,10 +101,7 @@ export const MyRSCComponent = async ({
### HTML field
The `lexicalHTMLField()` helper converts JSON to HTML and saves it in a field that is updated every time you read it via an `afterRead` hook. It's generally not recommended for two reasons:
1. It creates a column with duplicate content in another format.
2. In [client-side live preview](/docs/live-preview/client), it makes it not "live".
The `lexicalHTMLField()` helper converts JSON to HTML and saves it in a field that is updated every time you read it via an `afterRead` hook. It's generally not recommended, as it creates a column with duplicate content in another format.
Consider using the [on-demand HTML converter above](/docs/rich-text/converting-html#on-demand-recommended) or the [JSX converter](/docs/rich-text/converting-jsx) unless you have a good reason.

View File

@@ -269,11 +269,13 @@ Lexical does not generate accurate type definitions for your richText fields for
The Rich Text Field editor configuration has an `admin` property with the following options:
| Property | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
| **`hideInsertParagraphAtEnd`** | Set this property to `true` to hide the "+" button that appears at the end of the editor |
| Property | Description |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
| **`hideInsertParagraphAtEnd`** | Set this property to `true` to hide the "+" button that appears at the end of the editor. |
| **`hideDraggableBlockElement`** | Set this property to `true` to hide the draggable element that appears when you hover a node in the editor. |
| **`hideAddBlockButton`** | Set this property to `true` to hide the "+" button that appears when you hover a node in the editor. |
### Disable the gutter

View File

@@ -13,8 +13,8 @@ keywords: uploads, images, media, overview, documentation, Content Management Sy
</Banner>
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/upload-admin.jpg"
srcDark="https://payloadcms.com/images/docs/upload-admin.jpg"
srcLight="https://payloadcms.com/images/docs/uploads-overview.jpg"
srcDark="https://payloadcms.com/images/docs/uploads-overview.jpg"
alt="Shows an Upload enabled collection in the Payload Admin Panel"
caption="Admin Panel screenshot depicting a Media Collection with Upload enabled"
/>

View File

@@ -12,7 +12,7 @@ Extending on Payload's [Draft](/docs/versions/drafts) functionality, you can con
Autosave relies on Versions and Drafts being enabled in order to function.
</Banner>
![Autosave Enabled](/images/docs/autosave-enabled.png)
![Autosave Enabled](/images/docs/autosave-v3.jpg)
_If Autosave is enabled, drafts will be created automatically as the document is modified and the Admin UI adds an indicator describing when the document was last saved to the top right of the sidebar._
## Options

View File

@@ -14,7 +14,7 @@ Payload's Draft functionality builds on top of the Versions functionality to all
By enabling Versions with Drafts, your collections and globals can maintain _newer_, and _unpublished_ versions of your documents. It's perfect for cases where you might want to work on a document, update it and save your progress, but not necessarily make it publicly published right away. Drafts are extremely helpful when building preview implementations.
![Drafts Enabled](/images/docs/drafts-enabled.png)
![Drafts Enabled](/images/docs/autosave-drafts.jpg)
_If Drafts are enabled, the typical Save button is replaced with new actions which allow you to either save a draft, or publish your changes._
## Options

View File

@@ -13,7 +13,7 @@ keywords: version history, revisions, audit log, draft, publish, restore, autosa
When enabled, Payload will automatically scaffold a new Collection in your database to store versions of your document(s) over time, and the Admin UI will be extended with additional views that allow you to browse document versions, view diffs in order to see exactly what has changed in your documents (and when they changed), and restore documents back to prior versions easily.
![Versions](/images/docs/versions.png)
![Versions](/images/docs/versions-v3.jpg)
_Comparing an old version to a newer version of a document_
**With Versions, you can:**

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.51.0",
"version": "3.54.0",
"private": true,
"type": "module",
"workspaces": [

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/admin-bar",
"version": "3.51.0",
"version": "3.54.0",
"description": "An admin bar for React apps using Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.51.0",
"version": "3.54.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.51.0",
"version": "3.54.0",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -35,7 +35,12 @@ export const connect: Connect = async function connect(
}
try {
this.connection = (await mongoose.connect(urlToConnect, connectionOptions)).connection
if (!this.connection) {
this.connection = await mongoose.createConnection(urlToConnect, connectionOptions).asPromise()
}
await this.connection.openUri(urlToConnect, connectionOptions)
if (this.useAlternativeDropDatabase) {
if (this.connection.db) {
// Firestore doesn't support dropDatabase, so we monkey patch
@@ -75,7 +80,8 @@ export const connect: Connect = async function connect(
if (!hotReload) {
if (process.env.PAYLOAD_DROP_DATABASE === 'true') {
this.payload.logger.info('---- DROPPING DATABASE ----')
await mongoose.connection.dropDatabase()
await this.connection.dropDatabase()
this.payload.logger.info('---- DROPPED DATABASE ----')
}
}

View File

@@ -17,10 +17,16 @@ export const create: Create = async function create(
const options: CreateOptions = {
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
let doc
if (!data.createdAt) {
data.createdAt = new Date().toISOString()
}
transform({
adapter: this,
data,

View File

@@ -14,6 +14,10 @@ export const createGlobal: CreateGlobal = async function createGlobal(
) {
const { globalConfig, Model } = getGlobal({ adapter: this, globalSlug })
if (!data.createdAt) {
;(data as any).createdAt = new Date().toISOString()
}
transform({
adapter: this,
data,
@@ -24,6 +28,8 @@ export const createGlobal: CreateGlobal = async function createGlobal(
const options: CreateOptions = {
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
let [result] = (await Model.create([data], options)) as any

View File

@@ -12,6 +12,7 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
autosave,
createdAt,
globalSlug,
localeStatus,
parent,
publishedLocale,
req,
@@ -25,18 +26,24 @@ export const createGlobalVersion: CreateGlobalVersion = async function createGlo
const options = {
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const data = {
autosave,
createdAt,
latest: true,
localeStatus,
parent,
publishedLocale,
snapshot,
updatedAt,
version: versionData,
}
if (!data.createdAt) {
data.createdAt = new Date().toISOString()
}
const fields = buildVersionGlobalFields(this.payload.config, globalConfig)

View File

@@ -12,6 +12,7 @@ export const createVersion: CreateVersion = async function createVersion(
autosave,
collectionSlug,
createdAt,
localeStatus,
parent,
publishedLocale,
req,
@@ -29,18 +30,24 @@ export const createVersion: CreateVersion = async function createVersion(
const options = {
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const data = {
autosave,
createdAt,
latest: true,
localeStatus,
parent,
publishedLocale,
snapshot,
updatedAt,
version: versionData,
}
if (!data.createdAt) {
data.createdAt = new Date().toISOString()
}
const fields = buildVersionCollectionFields(this.payload.config, collectionConfig)

View File

@@ -1,11 +1,11 @@
import type { Destroy } from 'payload'
import mongoose from 'mongoose'
import type { MongooseAdapter } from './index.js'
export const destroy: Destroy = async function destroy(this: MongooseAdapter) {
await mongoose.disconnect()
await this.connection.close()
Object.keys(mongoose.models).map((model) => mongoose.deleteModel(model))
for (const name of Object.keys(this.connection.models)) {
this.connection.deleteModel(name)
}
}

View File

@@ -331,7 +331,7 @@ export function mongooseAdapter({
}
}
export { compatabilityOptions } from './utilities/compatabilityOptions.js'
export { compatibilityOptions } from './utilities/compatibilityOptions.js'
/**
* Attempt to find migrations directory.

View File

@@ -19,11 +19,14 @@ import { getBuildQueryPlugin } from './queries/getBuildQueryPlugin.js'
import { getDBName } from './utilities/getDBName.js'
export const init: Init = function init(this: MongooseAdapter) {
// Always create a scoped, **unopened** connection object
// (no URI here; models compile per-connection and do not require an open socket)
this.connection ??= mongoose.createConnection()
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schemaOptions = this.collectionsSchemaOptions?.[collection.slug]
const schema = buildCollectionSchema(collection, this.payload, schemaOptions)
if (collection.versions) {
const versionModelName = getDBName({ config: collection, versions: true })
@@ -55,7 +58,7 @@ export const init: Init = function init(this: MongooseAdapter) {
const versionCollectionName =
this.autoPluralization === true && !collection.dbName ? undefined : versionModelName
this.versions[collection.slug] = mongoose.model(
this.versions[collection.slug] = this.connection.model(
versionModelName,
versionSchema,
versionCollectionName,
@@ -66,14 +69,14 @@ export const init: Init = function init(this: MongooseAdapter) {
const collectionName =
this.autoPluralization === true && !collection.dbName ? undefined : modelName
this.collections[collection.slug] = mongoose.model<any>(
this.collections[collection.slug] = this.connection.model<any>(
modelName,
schema,
collectionName,
) as CollectionModel
})
this.globals = buildGlobalModel(this.payload) as GlobalModel
this.globals = buildGlobalModel(this) as GlobalModel
this.payload.config.globals.forEach((global) => {
if (global.versions) {
@@ -101,7 +104,7 @@ export const init: Init = function init(this: MongooseAdapter) {
}),
)
this.versions[global.slug] = mongoose.model<any>(
this.versions[global.slug] = this.connection.model<any>(
versionModelName,
versionSchema,
versionModelName,

View File

@@ -1,14 +1,13 @@
import type { Payload } from 'payload'
import mongoose from 'mongoose'
import type { MongooseAdapter } from '../index.js'
import type { GlobalModel } from '../types.js'
import { getBuildQueryPlugin } from '../queries/getBuildQueryPlugin.js'
import { buildSchema } from './buildSchema.js'
export const buildGlobalModel = (payload: Payload): GlobalModel | null => {
if (payload.config.globals && payload.config.globals.length > 0) {
export const buildGlobalModel = (adapter: MongooseAdapter): GlobalModel | null => {
if (adapter.payload.config.globals && adapter.payload.config.globals.length > 0) {
const globalsSchema = new mongoose.Schema(
{},
{ discriminatorKey: 'globalType', minimize: false, timestamps: true },
@@ -16,9 +15,13 @@ export const buildGlobalModel = (payload: Payload): GlobalModel | null => {
globalsSchema.plugin(getBuildQueryPlugin())
const Globals = mongoose.model('globals', globalsSchema, 'globals') as unknown as GlobalModel
const Globals = adapter.connection.model(
'globals',
globalsSchema,
'globals',
) as unknown as GlobalModel
Object.values(payload.config.globals).forEach((globalConfig) => {
Object.values(adapter.payload.config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema({
buildSchemaOptions: {
options: {
@@ -26,7 +29,7 @@ export const buildGlobalModel = (payload: Payload): GlobalModel | null => {
},
},
configFields: globalConfig.fields,
payload,
payload: adapter.payload,
})
Globals.discriminator(globalConfig.slug, globalSchema)
})

View File

@@ -63,7 +63,10 @@ const migrateModelWithBatching = async ({
},
},
})),
{ session },
{
session, // Timestamps are manually added by the write transform
timestamps: false,
},
)
skip += batchSize

View File

@@ -179,6 +179,13 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
for (let i = 0; i < result.docs.length; i++) {
const id = result.docs[i].parent
const localeStatus = result.docs[i].localeStatus || {}
if (locale && localeStatus[locale]) {
result.docs[i].status = localeStatus[locale]
result.docs[i].version._status = localeStatus[locale]
}
result.docs[i] = result.docs[i].version ?? {}
result.docs[i].id = id
}

View File

@@ -26,6 +26,8 @@ export const updateGlobal: UpdateGlobal = async function updateGlobal(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
transform({ adapter: this, data, fields, globalSlug, operation: 'write' })

View File

@@ -39,6 +39,8 @@ export async function updateGlobalVersion<T extends TypeWithID>(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const query = await buildQuery({

View File

@@ -1,4 +1,4 @@
import type { MongooseUpdateQueryOptions } from 'mongoose'
import type { MongooseUpdateQueryOptions, UpdateQuery } from 'mongoose'
import type { Job, UpdateJobs, Where } from 'payload'
import type { MongooseAdapter } from './index.js'
@@ -14,9 +14,13 @@ export const updateJobs: UpdateJobs = async function updateMany(
this: MongooseAdapter,
{ id, data, limit, req, returning, sort: sortArg, where: whereArg },
) {
if (!(data?.log as object[])?.length) {
if (
!(data?.log as object[])?.length &&
!(data.log && typeof data.log === 'object' && '$push' in data.log)
) {
delete data.log
}
const where = id ? { id: { equals: id } } : (whereArg as Where)
const { collectionConfig, Model } = getCollection({
@@ -36,6 +40,8 @@ export const updateJobs: UpdateJobs = async function updateMany(
lean: true,
new: true,
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
let query = await buildQuery({
@@ -45,17 +51,44 @@ export const updateJobs: UpdateJobs = async function updateMany(
where,
})
transform({ adapter: this, data, fields: collectionConfig.fields, operation: 'write' })
let updateData: UpdateQuery<any> = data
const $inc: Record<string, number> = {}
const $push: Record<string, { $each: any[] } | any> = {}
transform({
$inc,
$push,
adapter: this,
data,
fields: collectionConfig.fields,
operation: 'write',
})
const updateOps: UpdateQuery<any> = {}
if (Object.keys($inc).length) {
updateOps.$inc = $inc
}
if (Object.keys($push).length) {
updateOps.$push = $push
}
if (Object.keys(updateOps).length) {
updateOps.$set = updateData
updateData = updateOps
}
let result: Job[] = []
try {
if (id) {
if (returning === false) {
await Model.updateOne(query, data, options)
await Model.updateOne(query, updateData, options)
transform({ adapter: this, data, fields: collectionConfig.fields, operation: 'read' })
return null
} else {
const doc = await Model.findOneAndUpdate(query, data, options)
const doc = await Model.findOneAndUpdate(query, updateData, options)
result = doc ? [doc] : []
}
} else {
@@ -72,7 +105,7 @@ export const updateJobs: UpdateJobs = async function updateMany(
query = { _id: { $in: documentsToUpdate.map((doc) => doc._id) } }
}
await Model.updateMany(query, data, options)
await Model.updateMany(query, updateData, options)
if (returning === false) {
return null

View File

@@ -58,6 +58,8 @@ export const updateMany: UpdateMany = async function updateMany(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
let query = await buildQuery({

View File

@@ -38,6 +38,8 @@ export const updateOne: UpdateOne = async function updateOne(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const query = await buildQuery({
@@ -50,11 +52,24 @@ export const updateOne: UpdateOne = async function updateOne(
let result
const $inc: Record<string, number> = {}
let updateData: UpdateQuery<any> = data
transform({ $inc, adapter: this, data, fields, operation: 'write' })
const $inc: Record<string, number> = {}
const $push: Record<string, { $each: any[] } | any> = {}
transform({ $inc, $push, adapter: this, data, fields, operation: 'write' })
const updateOps: UpdateQuery<any> = {}
if (Object.keys($inc).length) {
updateData = { $inc, $set: updateData }
updateOps.$inc = $inc
}
if (Object.keys($push).length) {
updateOps.$push = $push
}
if (Object.keys(updateOps).length) {
updateOps.$set = updateData
updateData = updateOps
}
try {

View File

@@ -45,6 +45,8 @@ export const updateVersion: UpdateVersion = async function updateVersion(
select,
}),
session: await getSession(this, req),
// Timestamps are manually added by the write transform
timestamps: false,
}
const query = await buildQuery({

View File

@@ -2,9 +2,9 @@ import type { Args } from '../index.js'
/**
* Each key is a mongo-compatible database and the value
* is the recommended `mongooseAdapter` settings for compatability.
* is the recommended `mongooseAdapter` settings for compatibility.
*/
export const compatabilityOptions = {
export const compatibilityOptions = {
cosmosdb: {
transactionOptions: false,
useJoinAggregations: false,
@@ -12,6 +12,7 @@ export const compatabilityOptions = {
},
documentdb: {
disableIndexHints: true,
useJoinAggregations: false,
},
firestore: {
disableIndexHints: true,

View File

@@ -395,6 +395,10 @@ describe('transform', () => {
data,
fields: config.collections[0].fields,
})
if ('updatedAt' in data) {
delete data.updatedAt
}
const flattenValuesAfter = Object.values(flattenRelationshipValues(data))
flattenValuesAfter.forEach((value, i) => {

View File

@@ -209,6 +209,7 @@ const sanitizeDate = ({
type Args = {
$inc?: Record<string, number>
$push?: Record<string, { $each: any[] } | any>
/** instance of the adapter */
adapter: MongooseAdapter
/** data to transform, can be an array of documents or a single document */
@@ -398,6 +399,7 @@ const stripFields = ({
export const transform = ({
$inc,
$push,
adapter,
data,
fields,
@@ -412,7 +414,16 @@ export const transform = ({
if (Array.isArray(data)) {
for (const item of data) {
transform({ $inc, adapter, data: item, fields, globalSlug, operation, validateRelationships })
transform({
$inc,
$push,
adapter,
data: item,
fields,
globalSlug,
operation,
validateRelationships,
})
}
return
}
@@ -470,6 +481,39 @@ export const transform = ({
}
}
if (
$push &&
field.type === 'array' &&
operation === 'write' &&
field.name in ref &&
ref[field.name]
) {
const value = ref[field.name]
if (value && typeof value === 'object' && '$push' in value) {
const push = value.$push
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
if (typeof push === 'object' && push !== null) {
Object.entries(push).forEach(([localeKey, localeData]) => {
if (Array.isArray(localeData)) {
$push[`${parentPath}${field.name}.${localeKey}`] = { $each: localeData }
} else if (typeof localeData === 'object') {
$push[`${parentPath}${field.name}.${localeKey}`] = localeData
}
})
}
} else {
if (Array.isArray(push)) {
$push[`${parentPath}${field.name}`] = { $each: push }
} else if (typeof push === 'object') {
$push[`${parentPath}${field.name}`] = push
}
}
delete ref[field.name]
}
}
if (field.type === 'date' && operation === 'read' && field.name in ref && ref[field.name]) {
if (config.localization && fieldShouldBeLocalized({ field, parentIsLocalized })) {
const fieldRef = ref[field.name] as Record<string, unknown>
@@ -548,4 +592,15 @@ export const transform = ({
parentIsLocalized,
ref: data,
})
if (operation === 'write') {
if (typeof data.updatedAt === 'undefined') {
// If data.updatedAt is explicitly set to `null` we should not set it - this means we don't want to change the value of updatedAt.
data.updatedAt = new Date().toISOString()
} else if (data.updatedAt === null) {
// `updatedAt` may be explicitly set to null to disable updating it - if that is the case, we need to delete the property. Keeping it as null will
// cause the database to think we want to set it to null, which we don't.
delete data.updatedAt
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.51.0",
"version": "3.54.0",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.51.0",
"version": "3.54.0",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-vercel-postgres",
"version": "3.51.0",
"version": "3.54.0",
"description": "Vercel Postgres adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.51.0",
"version": "3.54.0",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -15,6 +15,7 @@ export async function createGlobalVersion<T extends TypeWithID>(
autosave,
createdAt,
globalSlug,
localeStatus,
publishedLocale,
req,
returning,
@@ -35,6 +36,7 @@ export async function createGlobalVersion<T extends TypeWithID>(
autosave,
createdAt,
latest: true,
localeStatus,
publishedLocale,
snapshot,
updatedAt,

View File

@@ -15,6 +15,7 @@ export async function createVersion<T extends TypeWithID>(
autosave,
collectionSlug,
createdAt,
localeStatus,
parent,
publishedLocale,
req,
@@ -40,6 +41,7 @@ export async function createVersion<T extends TypeWithID>(
autosave,
createdAt,
latest: true,
localeStatus,
parent,
publishedLocale,
snapshot,

View File

@@ -791,9 +791,14 @@ export const traverseFields = ({
} else {
shouldSelect = true
}
const tableName = fieldShouldBeLocalized({ field, parentIsLocalized })
? `${currentTableName}${adapter.localesSuffix}`
: currentTableName
if (shouldSelect) {
args.extras[name] = sql.raw(`ST_AsGeoJSON(${toSnakeCase(name)})::jsonb`).as(name)
args.extras[name] = sql
.raw(`ST_AsGeoJSON("${adapter.tables[tableName][name].name}")::jsonb`)
.as(name)
}
break
}

View File

@@ -110,19 +110,32 @@ export const sanitizeQueryValue = ({
}
}
if (field.type === 'date' && operator !== 'exists') {
if (typeof val === 'string') {
if (val === 'null' || val === '') {
formattedValue = null
} else {
const date = new Date(val)
if (Number.isNaN(date.getTime())) {
return { operator, value: undefined }
}
formattedValue = date.toISOString()
// Helper function to convert a single date value to ISO string
const convertDateToISO = (item: unknown): unknown => {
if (typeof item === 'string') {
if (item === 'null' || item === '') {
return null
}
} else if (typeof val === 'number') {
formattedValue = new Date(val).toISOString()
const date = new Date(item)
return Number.isNaN(date.getTime()) ? undefined : date.toISOString()
} else if (typeof item === 'number') {
return new Date(item).toISOString()
} else if (item instanceof Date) {
return item.toISOString()
}
return item
}
if (field.type === 'date' && operator !== 'exists') {
if (Array.isArray(formattedValue)) {
// Handle arrays of dates for 'in' and 'not_in' operators
formattedValue = formattedValue.map(convertDateToISO).filter((item) => item !== undefined)
} else {
const converted = convertDateToISO(val)
if (converted === undefined) {
return { operator, value: undefined }
}
formattedValue = converted
}
}

View File

@@ -36,15 +36,17 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
where: combinedWhere,
})
return {
...result,
docs: result.docs.map((doc) => {
doc = {
id: doc.parent,
...doc.version,
}
for (let i = 0; i < result.docs.length; i++) {
const id = result.docs[i].parent
const localeStatus = result.docs[i].localeStatus || {}
if (locale && localeStatus[locale]) {
result.docs[i].status = localeStatus[locale]
result.docs[i].version._status = localeStatus[locale]
}
return doc
}),
result.docs[i] = result.docs[i].version ?? {}
result.docs[i].id = id
}
return result
}

View File

@@ -71,6 +71,7 @@ export const transformArray = ({
data.forEach((arrayRow, i) => {
const newRow: ArrayRowToInsert = {
arrays: {},
arraysToPush: {},
locales: {},
row: {
_order: i + 1,
@@ -104,6 +105,7 @@ export const transformArray = ({
traverseFields({
adapter,
arrays: newRow.arrays,
arraysToPush: newRow.arraysToPush,
baseTableName,
blocks,
blocksToDelete,

View File

@@ -78,6 +78,7 @@ export const transformBlocks = ({
const newRow: BlockRowToInsert = {
arrays: {},
arraysToPush: {},
locales: {},
row: {
_order: i + 1,
@@ -116,6 +117,7 @@ export const transformBlocks = ({
traverseFields({
adapter,
arrays: newRow.arrays,
arraysToPush: newRow.arraysToPush,
baseTableName,
blocks,
blocksToDelete,

View File

@@ -27,6 +27,7 @@ export const transformForWrite = ({
// Split out the incoming data into rows to insert / delete
const rowToInsert: RowToInsert = {
arrays: {},
arraysToPush: {},
blocks: {},
blocksToDelete: new Set(),
locales: {},
@@ -45,6 +46,7 @@ export const transformForWrite = ({
traverseFields({
adapter,
arrays: rowToInsert.arrays,
arraysToPush: rowToInsert.arraysToPush,
baseTableName: tableName,
blocks: rowToInsert.blocks,
blocksToDelete: rowToInsert.blocksToDelete,

View File

@@ -4,13 +4,7 @@ import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
import toSnakeCase from 'to-snake-case'
import type { DrizzleAdapter } from '../../types.js'
import type {
ArrayRowToInsert,
BlockRowToInsert,
NumberToDelete,
RelationshipToDelete,
TextToDelete,
} from './types.js'
import type { NumberToDelete, RelationshipToDelete, RowToInsert, TextToDelete } from './types.js'
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
@@ -23,16 +17,20 @@ import { transformTexts } from './texts.js'
type Args = {
adapter: DrizzleAdapter
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
/**
* This will delete the array table and then re-insert all the new array rows.
*/
arrays: RowToInsert['arrays']
/**
* Array rows to push to the existing array. This will simply create
* a new row in the array table.
*/
arraysToPush: RowToInsert['arraysToPush']
/**
* This is the name of the base table
*/
baseTableName: string
blocks: {
[blockType: string]: BlockRowToInsert[]
}
blocks: RowToInsert['blocks']
blocksToDelete: Set<string>
/**
* A snake-case field prefix, representing prior fields
@@ -82,6 +80,7 @@ type Args = {
export const traverseFields = ({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
@@ -129,13 +128,24 @@ export const traverseFields = ({
if (field.type === 'array') {
const arrayTableName = adapter.tableNameMap.get(`${parentTableName}_${columnName}`)
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = []
}
if (isLocalized) {
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
let value: {
[locale: string]: unknown[]
} = data[field.name] as any
let push = false
if (typeof value === 'object' && '$push' in value) {
value = value.$push as any
push = true
}
if (typeof value === 'object' && value !== null) {
Object.entries(value).forEach(([localeKey, _localeData]) => {
let localeData = _localeData
if (push && !Array.isArray(localeData)) {
localeData = [localeData]
}
if (Array.isArray(localeData)) {
const newRows = transformArray({
adapter,
@@ -158,18 +168,35 @@ export const traverseFields = ({
withinArrayOrBlockLocale: localeKey,
})
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
if (push) {
if (!arraysToPush[arrayTableName]) {
arraysToPush[arrayTableName] = []
}
arraysToPush[arrayTableName] = arraysToPush[arrayTableName].concat(newRows)
} else {
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = []
}
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
}
}
})
}
} else {
let value = data[field.name]
let push = false
if (typeof value === 'object' && '$push' in value) {
value = Array.isArray(value.$push) ? value.$push : [value.$push]
push = true
}
const newRows = transformArray({
adapter,
arrayTableName,
baseTableName,
blocks,
blocksToDelete,
data: data[field.name],
data: value,
field,
numbers,
numbersToDelete,
@@ -183,7 +210,17 @@ export const traverseFields = ({
withinArrayOrBlockLocale,
})
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
if (push) {
if (!arraysToPush[arrayTableName]) {
arraysToPush[arrayTableName] = []
}
arraysToPush[arrayTableName] = arraysToPush[arrayTableName].concat(newRows)
} else {
if (!arrays[arrayTableName]) {
arrays[arrayTableName] = []
}
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
}
}
return
@@ -264,6 +301,7 @@ export const traverseFields = ({
traverseFields({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
@@ -298,6 +336,7 @@ export const traverseFields = ({
traverseFields({
adapter,
arrays,
arraysToPush,
baseTableName,
blocks,
blocksToDelete,
@@ -546,6 +585,19 @@ export const traverseFields = ({
valuesToTransform.forEach(({ localeKey, ref, value }) => {
let formattedValue = value
if (field.type === 'date') {
if (fieldName === 'updatedAt' && typeof formattedValue === 'undefined') {
// let the db handle this. If formattedValue is explicitly set to `null` we should not set it - this means we don't want to change the value of updatedAt.
formattedValue = new Date().toISOString()
} else {
if (typeof value === 'number' && !Number.isNaN(value)) {
formattedValue = new Date(value).toISOString()
} else if (value instanceof Date) {
formattedValue = value.toISOString()
}
}
}
if (typeof value !== 'undefined') {
if (value && field.type === 'point' && adapter.name !== 'sqlite') {
formattedValue = sql`ST_GeomFromGeoJSON(${JSON.stringify(value)})`
@@ -570,19 +622,6 @@ export const traverseFields = ({
formattedValue = sql.raw(`${columnName} + ${value.$inc}`)
}
if (field.type === 'date') {
if (typeof value === 'number' && !Number.isNaN(value)) {
formattedValue = new Date(value).toISOString()
} else if (value instanceof Date) {
formattedValue = value.toISOString()
}
}
}
if (field.type === 'date' && fieldName === 'updatedAt') {
// let the db handle this
formattedValue = new Date().toISOString()
}
if (typeof formattedValue !== 'undefined') {

View File

@@ -2,6 +2,9 @@ export type ArrayRowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
arraysToPush: {
[tableName: string]: ArrayRowToInsert[]
}
locales: {
[locale: string]: Record<string, unknown>
}
@@ -12,6 +15,9 @@ export type BlockRowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
arraysToPush: {
[tableName: string]: ArrayRowToInsert[]
}
locales: {
[locale: string]: Record<string, unknown>
}
@@ -37,6 +43,9 @@ export type RowToInsert = {
arrays: {
[tableName: string]: ArrayRowToInsert[]
}
arraysToPush: {
[tableName: string]: ArrayRowToInsert[]
}
blocks: {
[tableName: string]: BlockRowToInsert[]
}

View File

@@ -13,9 +13,13 @@ export const updateJobs: UpdateJobs = async function updateMany(
this: DrizzleAdapter,
{ id, data, limit: limitArg, req, returning, sort: sortArg, where: whereArg },
) {
if (!(data?.log as object[])?.length) {
if (
!(data?.log as object[])?.length &&
!(data.log && typeof data.log === 'object' && '$push' in data.log)
) {
delete data.log
}
const whereToUse: Where = id ? { id: { equals: id } } : whereArg
const limit = id ? 1 : limitArg

View File

@@ -42,23 +42,54 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
upsertTarget,
where,
}: Args): Promise<T> => {
if (operation === 'create' && !data.createdAt) {
data.createdAt = new Date().toISOString()
}
let insertedRow: Record<string, unknown> = { id }
if (id && shouldUseOptimizedUpsertRow({ data, fields })) {
const { row } = transformForWrite({
const transformedForWrite = transformForWrite({
adapter,
data,
enableAtomicWrites: true,
fields,
tableName,
})
const { row } = transformedForWrite
const { arraysToPush } = transformedForWrite
const drizzle = db as LibSQLDatabase
// First, handle $push arrays
if (arraysToPush && Object.keys(arraysToPush)?.length) {
await insertArrays({
adapter,
arrays: [arraysToPush],
db,
parentRows: [insertedRow],
uuidMap: {},
})
}
// If row.updatedAt is not set, delete it to avoid triggering hasDataToUpdate. `updatedAt` may be explicitly set to null to
// disable triggering hasDataToUpdate.
if (typeof row.updatedAt === 'undefined' || row.updatedAt === null) {
delete row.updatedAt
}
const hasDataToUpdate = row && Object.keys(row)?.length
// Then, handle regular row update
if (ignoreResult) {
await drizzle
.update(adapter.tables[tableName])
.set(row)
.where(eq(adapter.tables[tableName].id, id))
if (hasDataToUpdate) {
// Only update row if there is something to update.
// Example: if the data only consists of a single $push, calling insertArrays is enough - we don't need to update the row.
await drizzle
.update(adapter.tables[tableName])
.set(row)
.where(eq(adapter.tables[tableName].id, id))
}
return ignoreResult === 'idOnly' ? ({ id } as T) : null
}
@@ -74,6 +105,22 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
const findManyKeysLength = Object.keys(findManyArgs).length
const hasOnlyColumns = Object.keys(findManyArgs.columns || {}).length > 0
if (!hasDataToUpdate) {
// Nothing to update => just fetch current row and return
findManyArgs.where = eq(adapter.tables[tableName].id, insertedRow.id)
const doc = await db.query[tableName].findFirst(findManyArgs)
return transform<T>({
adapter,
config: adapter.payload.config,
data: doc,
fields,
joinQuery: false,
tableName,
})
}
if (findManyKeysLength === 0 || hasOnlyColumns) {
// Optimization - No need for joins => can simply use returning(). This is optimal for very simple collections
// without complex fields that live in separate tables like blocks, arrays, relationships, etc.
@@ -429,9 +476,9 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
await insertArrays({
adapter,
arrays: [rowToInsert.arrays],
arrays: [rowToInsert.arrays, rowToInsert.arraysToPush],
db,
parentRows: [insertedRow],
parentRows: [insertedRow, insertedRow],
uuidMap: arraysBlocksUUIDMap,
})

View File

@@ -32,6 +32,9 @@ export const insertArrays = async ({
const rowsByTable: RowsByTable = {}
arrays.forEach((arraysByTable, parentRowIndex) => {
if (!arraysByTable || Object.keys(arraysByTable).length === 0) {
return
}
Object.entries(arraysByTable).forEach(([tableName, arrayRows]) => {
// If the table doesn't exist in map, initialize it
if (!rowsByTable[tableName]) {

View File

@@ -20,7 +20,6 @@ export const shouldUseOptimizedUpsertRow = ({
}
if (
field.type === 'array' ||
field.type === 'blocks' ||
((field.type === 'text' ||
field.type === 'relationship' ||
@@ -35,6 +34,17 @@ export const shouldUseOptimizedUpsertRow = ({
return false
}
if (field.type === 'array') {
if (typeof value === 'object' && '$push' in value && value.$push) {
return shouldUseOptimizedUpsertRow({
// Only check first row - this function cares about field definitions. Each array row will have the same field definitions.
data: Array.isArray(value.$push) ? value.$push?.[0] : value.$push,
fields: field.flattenedFields,
})
}
return false
}
if (
(field.type === 'group' || field.type === 'tab') &&
value &&

View File

@@ -9,7 +9,12 @@ export const buildIndexName = ({
name: string
number?: number
}): string => {
const indexName = `${name}${number ? `_${number}` : ''}_idx`
let indexName = `${name}${number ? `_${number}` : ''}_idx`
if (indexName.length > 60) {
const suffix = `${number ? `_${number}` : ''}_idx`
indexName = `${name.slice(0, 60 - suffix.length)}${suffix}`
}
if (!adapter.indexes.has(indexName)) {
adapter.indexes.add(indexName)

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.51.0",
"version": "3.54.0",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.51.0",
"version": "3.54.0",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.51.0",
"version": "3.54.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -22,6 +22,7 @@ export const formatName = (string: string): string => {
.replace(/\)/g, '_')
.replace(/'/g, '_')
.replace(/ /g, '')
.replace(/\[|\]/g, '_')
return formatted || '_'
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.51.0",
"version": "3.54.0",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.51.0",
"version": "3.54.0",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.51.0",
"version": "3.54.0",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,22 +1,12 @@
import type { FieldSchemaJSON } from 'payload'
import type { CollectionPopulationRequestHandler, LivePreviewMessageEvent } from './types.js'
import { isLivePreviewEvent } from './isLivePreviewEvent.js'
import { mergeData } from './mergeData.js'
const _payloadLivePreview: {
fieldSchema: FieldSchemaJSON | undefined
// eslint-disable-next-line @typescript-eslint/no-explicit-any
previousData: any
} = {
/**
* For performance reasons, `fieldSchemaJSON` will only be sent once on the initial message
* We need to cache this value so that it can be used across subsequent messages
* To do this, save `fieldSchemaJSON` when it arrives as a global variable
* Send this cached value to `mergeData`, instead of `eventData.fieldSchemaJSON` directly
*/
fieldSchema: undefined,
/**
* Each time the data is merged, cache the result as a `previousData` variable
* This will ensure changes compound overtop of each other
@@ -35,26 +25,13 @@ export const handleMessage = async <T extends Record<string, any>>(args: {
const { apiRoute, depth, event, initialData, requestHandler, serverURL } = args
if (isLivePreviewEvent(event, serverURL)) {
const { data, externallyUpdatedRelationship, fieldSchemaJSON, locale } = event.data
if (!_payloadLivePreview?.fieldSchema && fieldSchemaJSON) {
_payloadLivePreview.fieldSchema = fieldSchemaJSON
}
if (!_payloadLivePreview?.fieldSchema) {
// eslint-disable-next-line no-console
console.warn(
'Payload Live Preview: No `fieldSchemaJSON` was received from the parent window. Unable to merge data.',
)
return initialData
}
const { collectionSlug, data, globalSlug, locale } = event.data
const mergedData = await mergeData<T>({
apiRoute,
collectionSlug,
depth,
externallyUpdatedRelationship,
fieldSchema: _payloadLivePreview.fieldSchema,
globalSlug,
incomingData: data,
initialData: _payloadLivePreview?.previousData || initialData,
locale,

View File

@@ -4,6 +4,5 @@ export { isLivePreviewEvent } from './isLivePreviewEvent.js'
export { mergeData } from './mergeData.js'
export { ready } from './ready.js'
export { subscribe } from './subscribe.js'
export { traverseRichText } from './traverseRichText.js'
export type { LivePreviewMessageEvent } from './types.js'
export { unsubscribe } from './unsubscribe.js'

View File

@@ -1,115 +1,60 @@
import type { DocumentEvent, FieldSchemaJSON, PaginatedDocs } from 'payload'
import type { CollectionPopulationRequestHandler } from './types.js'
import type { CollectionPopulationRequestHandler, PopulationsByCollection } from './types.js'
import { traverseFields } from './traverseFields.js'
const defaultRequestHandler = ({
const defaultRequestHandler: CollectionPopulationRequestHandler = ({
apiPath,
data,
endpoint,
serverURL,
}: {
apiPath: string
endpoint: string
serverURL: string
}) => {
const url = `${serverURL}${apiPath}/${endpoint}`
return fetch(url, {
body: JSON.stringify(data),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Payload-HTTP-Method-Override': 'GET',
},
method: 'POST',
})
}
// Relationships are only updated when their `id` or `relationTo` changes, by comparing the old and new values
// This needs to also happen when locale changes, except this is not not part of the API response
// Instead, we keep track of the old locale ourselves and trigger a re-population when it changes
let prevLocale: string | undefined
export const mergeData = async <T extends Record<string, any>>(args: {
apiRoute?: string
/**
* @deprecated Use `requestHandler` instead
*/
collectionPopulationRequestHandler?: CollectionPopulationRequestHandler
collectionSlug?: string
depth?: number
externallyUpdatedRelationship?: DocumentEvent
fieldSchema: FieldSchemaJSON
globalSlug?: string
incomingData: Partial<T>
initialData: T
locale?: string
requestHandler?: CollectionPopulationRequestHandler
returnNumberOfRequests?: boolean
serverURL: string
}): Promise<
{
_numberOfRequests?: number
} & T
> => {
}): Promise<T> => {
const {
apiRoute,
collectionSlug,
depth,
externallyUpdatedRelationship,
fieldSchema,
globalSlug,
incomingData,
initialData,
locale,
returnNumberOfRequests,
serverURL,
} = args
const result = { ...initialData }
const requestHandler = args.requestHandler || defaultRequestHandler
const populationsByCollection: PopulationsByCollection = {}
const result = await requestHandler({
apiPath: apiRoute || '/api',
data: {
data: incomingData,
depth,
locale,
},
endpoint: encodeURI(
`${globalSlug ? 'globals/' : ''}${collectionSlug ?? globalSlug}${collectionSlug ? `/${initialData.id}` : ''}`,
),
serverURL,
}).then((res) => res.json())
traverseFields({
externallyUpdatedRelationship,
fieldSchema,
incomingData,
localeChanged: prevLocale !== locale,
populationsByCollection,
result,
})
await Promise.all(
Object.entries(populationsByCollection).map(async ([collection, populations]) => {
let res: PaginatedDocs
const ids = new Set(populations.map(({ id }) => id))
const requestHandler =
args.collectionPopulationRequestHandler || args.requestHandler || defaultRequestHandler
try {
res = await requestHandler({
apiPath: apiRoute || '/api',
endpoint: encodeURI(
`${collection}?depth=${depth}&limit=${ids.size}&where[id][in]=${Array.from(ids).join(',')}${locale ? `&locale=${locale}` : ''}`,
),
serverURL,
}).then((res) => res.json())
if (res?.docs?.length > 0) {
res.docs.forEach((doc) => {
populationsByCollection[collection]?.forEach((population) => {
if (population.id === doc.id) {
population.ref[population.accessor] = doc
}
})
})
}
} catch (err) {
console.error(err) // eslint-disable-line no-console
}
}),
)
prevLocale = locale
return {
...result,
...(returnNumberOfRequests
? { _numberOfRequests: Object.keys(populationsByCollection).length }
: {}),
}
return result
}

View File

@@ -1,299 +0,0 @@
import type { DocumentEvent, FieldSchemaJSON } from 'payload'
import type { PopulationsByCollection } from './types.js'
import { traverseRichText } from './traverseRichText.js'
export const traverseFields = <T extends Record<string, any>>(args: {
externallyUpdatedRelationship?: DocumentEvent
fieldSchema: FieldSchemaJSON
incomingData: T
localeChanged: boolean
populationsByCollection: PopulationsByCollection
result: Record<string, any>
}): void => {
const {
externallyUpdatedRelationship,
fieldSchema: fieldSchemas,
incomingData,
localeChanged,
populationsByCollection,
result,
} = args
fieldSchemas.forEach((fieldSchema) => {
if ('name' in fieldSchema && typeof fieldSchema.name === 'string') {
const fieldName = fieldSchema.name
switch (fieldSchema.type) {
case 'array':
if (
!incomingData[fieldName] &&
incomingData[fieldName] !== undefined &&
result?.[fieldName] !== undefined
) {
result[fieldName] = []
}
if (Array.isArray(incomingData[fieldName])) {
result[fieldName] = incomingData[fieldName].map((incomingRow, i) => {
if (!result[fieldName]) {
result[fieldName] = []
}
if (!result[fieldName][i]) {
result[fieldName][i] = {}
}
traverseFields({
externallyUpdatedRelationship,
fieldSchema: fieldSchema.fields!,
incomingData: incomingRow,
localeChanged,
populationsByCollection,
result: result[fieldName][i],
})
return result[fieldName][i]
})
}
break
case 'blocks':
if (Array.isArray(incomingData[fieldName])) {
result[fieldName] = incomingData[fieldName].map((incomingBlock, i) => {
const incomingBlockJSON = fieldSchema.blocks?.[incomingBlock.blockType]
if (!result[fieldName]) {
result[fieldName] = []
}
if (
!result[fieldName][i] ||
result[fieldName][i].id !== incomingBlock.id ||
result[fieldName][i].blockType !== incomingBlock.blockType
) {
result[fieldName][i] = {
blockType: incomingBlock.blockType,
}
}
traverseFields({
externallyUpdatedRelationship,
fieldSchema: incomingBlockJSON!.fields!,
incomingData: incomingBlock,
localeChanged,
populationsByCollection,
result: result[fieldName][i],
})
return result[fieldName][i]
})
} else {
result[fieldName] = []
}
break
case 'group':
// falls through
case 'tabs':
if (!result[fieldName]) {
result[fieldName] = {}
}
traverseFields({
externallyUpdatedRelationship,
fieldSchema: fieldSchema.fields!,
incomingData: incomingData[fieldName] || {},
localeChanged,
populationsByCollection,
result: result[fieldName],
})
break
case 'relationship':
// falls through
case 'upload':
// Handle `hasMany` relationships
if (fieldSchema.hasMany && Array.isArray(incomingData[fieldName])) {
if (!result[fieldName] || !incomingData[fieldName].length) {
result[fieldName] = []
}
incomingData[fieldName].forEach((incomingRelation, i) => {
// Handle `hasMany` polymorphic
if (Array.isArray(fieldSchema.relationTo)) {
// if the field doesn't exist on the result, create it
// the value will be populated later
if (!result[fieldName][i]) {
result[fieldName][i] = {
relationTo: incomingRelation.relationTo,
}
}
const oldID = result[fieldName][i]?.value?.id
const oldRelation = result[fieldName][i]?.relationTo
const newID = incomingRelation.value
const newRelation = incomingRelation.relationTo
const hasChanged = newID !== oldID || newRelation !== oldRelation
const hasUpdated =
newRelation === externallyUpdatedRelationship?.entitySlug &&
newID === externallyUpdatedRelationship?.id
if (hasChanged || hasUpdated || localeChanged) {
if (!populationsByCollection[newRelation]) {
populationsByCollection[newRelation] = []
}
populationsByCollection[newRelation].push({
id: incomingRelation.value,
accessor: 'value',
ref: result[fieldName][i],
})
}
} else {
// Handle `hasMany` monomorphic
const hasChanged = incomingRelation !== result[fieldName][i]?.id
const hasUpdated =
fieldSchema.relationTo === externallyUpdatedRelationship?.entitySlug &&
incomingRelation === externallyUpdatedRelationship?.id
if (hasChanged || hasUpdated || localeChanged) {
if (!populationsByCollection[fieldSchema.relationTo!]) {
populationsByCollection[fieldSchema.relationTo!] = []
}
populationsByCollection[fieldSchema.relationTo!]?.push({
id: incomingRelation,
accessor: i,
ref: result[fieldName],
})
}
}
})
} else {
// Handle `hasOne` polymorphic
if (Array.isArray(fieldSchema.relationTo)) {
// if the field doesn't exist on the result, create it
// the value will be populated later
if (!result[fieldName]) {
result[fieldName] = {
relationTo: incomingData[fieldName]?.relationTo,
}
}
const hasNewValue =
incomingData[fieldName] &&
typeof incomingData[fieldName] === 'object' &&
incomingData[fieldName] !== null
const hasOldValue =
result[fieldName] &&
typeof result[fieldName] === 'object' &&
result[fieldName] !== null
const newID = hasNewValue
? typeof incomingData[fieldName].value === 'object'
? incomingData[fieldName].value.id
: incomingData[fieldName].value
: ''
const oldID = hasOldValue
? typeof result[fieldName].value === 'object'
? result[fieldName].value.id
: result[fieldName].value
: ''
const newRelation = hasNewValue ? incomingData[fieldName].relationTo : ''
const oldRelation = hasOldValue ? result[fieldName].relationTo : ''
const hasChanged = newID !== oldID || newRelation !== oldRelation
const hasUpdated =
newRelation === externallyUpdatedRelationship?.entitySlug &&
newID === externallyUpdatedRelationship?.id
// if the new value/relation is different from the old value/relation
// populate the new value, otherwise leave it alone
if (hasChanged || hasUpdated || localeChanged) {
// if the new value is not empty, populate it
// otherwise set the value to null
if (newID) {
if (!populationsByCollection[newRelation]) {
populationsByCollection[newRelation] = []
}
populationsByCollection[newRelation].push({
id: newID,
accessor: 'value',
ref: result[fieldName],
})
} else {
result[fieldName] = null
}
}
} else {
// Handle `hasOne` monomorphic
const newID: number | string | undefined =
(incomingData[fieldName] &&
typeof incomingData[fieldName] === 'object' &&
incomingData[fieldName].id) ||
incomingData[fieldName]
const oldID: number | string | undefined =
(result[fieldName] &&
typeof result[fieldName] === 'object' &&
result[fieldName].id) ||
result[fieldName]
const hasChanged = newID !== oldID
const hasUpdated =
fieldSchema.relationTo === externallyUpdatedRelationship?.entitySlug &&
newID === externallyUpdatedRelationship?.id
// if the new value is different from the old value
// populate the new value, otherwise leave it alone
if (hasChanged || hasUpdated || localeChanged) {
// if the new value is not empty, populate it
// otherwise set the value to null
if (newID) {
if (!populationsByCollection[fieldSchema.relationTo!]) {
populationsByCollection[fieldSchema.relationTo!] = []
}
populationsByCollection[fieldSchema.relationTo!]?.push({
id: newID,
accessor: fieldName,
ref: result as Record<string, unknown>,
})
} else {
result[fieldName] = null
}
}
}
}
break
case 'richText':
result[fieldName] = traverseRichText({
externallyUpdatedRelationship,
incomingData: incomingData[fieldName],
populationsByCollection,
result: result[fieldName],
})
break
default:
result[fieldName] = incomingData[fieldName]
}
}
})
}

View File

@@ -1,105 +0,0 @@
import type { DocumentEvent } from 'payload'
import type { PopulationsByCollection } from './types.js'
export const traverseRichText = ({
externallyUpdatedRelationship,
incomingData,
populationsByCollection,
result,
}: {
externallyUpdatedRelationship?: DocumentEvent
incomingData: any
populationsByCollection: PopulationsByCollection
result: any
}): any => {
if (Array.isArray(incomingData)) {
if (!result) {
result = []
}
result = incomingData.map((item, index) => {
if (!result[index]) {
result[index] = item
}
return traverseRichText({
externallyUpdatedRelationship,
incomingData: item,
populationsByCollection,
result: result[index],
})
})
} else if (incomingData && typeof incomingData === 'object') {
if (!result) {
result = {}
}
// Remove keys from `result` that do not appear in `incomingData`
// There's likely another way to do this,
// But recursion and references make this very difficult
Object.keys(result).forEach((key) => {
if (!(key in incomingData)) {
delete result[key]
}
})
// Iterate over the keys of `incomingData` and populate `result`
Object.keys(incomingData).forEach((key) => {
if (!result[key]) {
// Instantiate the key in `result` if it doesn't exist
// Ensure its type matches the type of the `incomingData`
// We don't have a schema to check against here
result[key] =
incomingData[key] && typeof incomingData[key] === 'object'
? Array.isArray(incomingData[key])
? []
: {}
: undefined
}
const isRelationship = key === 'value' && 'relationTo' in incomingData
if (isRelationship) {
// or if there are no keys besides id
const needsPopulation =
!result.value ||
typeof result.value !== 'object' ||
(typeof result.value === 'object' &&
Object.keys(result.value).length === 1 &&
'id' in result.value)
const hasChanged =
result &&
typeof result === 'object' &&
result.value.id === externallyUpdatedRelationship?.id
if (needsPopulation || hasChanged) {
if (!populationsByCollection[incomingData.relationTo]) {
populationsByCollection[incomingData.relationTo] = []
}
populationsByCollection[incomingData.relationTo]?.push({
id:
incomingData[key] && typeof incomingData[key] === 'object'
? incomingData[key].id
: incomingData[key],
accessor: 'value',
ref: result,
})
}
} else {
result[key] = traverseRichText({
externallyUpdatedRelationship,
incomingData: incomingData[key],
populationsByCollection,
result: result[key],
})
}
})
} else {
result = incomingData
}
return result
}

View File

@@ -1,11 +1,13 @@
import type { DocumentEvent, FieldSchemaJSON } from 'payload'
import type { DocumentEvent } from 'payload'
export type CollectionPopulationRequestHandler = ({
apiPath,
data,
endpoint,
serverURL,
}: {
apiPath: string
data: Record<string, any>
endpoint: string
serverURL: string
}) => Promise<Response>
@@ -14,18 +16,11 @@ export type LivePreviewArgs = {}
export type LivePreview = void
export type PopulationsByCollection = {
[slug: string]: Array<{
accessor: number | string
id: number | string
ref: Record<string, unknown>
}>
}
export type LivePreviewMessageEvent<T> = MessageEvent<{
collectionSlug?: string
data: T
externallyUpdatedRelationship?: DocumentEvent
fieldSchemaJSON: FieldSchemaJSON
globalSlug?: string
locale?: string
type: 'payload-live-preview'
}>

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.51.0",
"version": "3.54.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -17,6 +17,11 @@ export async function refresh({ config }: { config: any }) {
throw new Error('Cannot refresh token: user not authenticated')
}
const existingCookie = await getExistingAuthToken(payload.config.cookiePrefix)
if (!existingCookie) {
return { message: 'No valid token found to refresh', success: false }
}
const collection: CollectionSlug | undefined = result.user.collection
const collectionConfig = payload.collections[collection]
@@ -35,15 +40,10 @@ export async function refresh({ config }: { config: any }) {
return { message: 'Token refresh failed', success: false }
}
const existingCookie = await getExistingAuthToken(payload.config.cookiePrefix)
if (!existingCookie) {
return { message: 'No valid token found to refresh', success: false }
}
await setPayloadAuthCookie({
authConfig: collectionConfig.config.auth,
cookiePrefix: payload.config.cookiePrefix,
token: existingCookie.value,
token: refreshResult.refreshedToken,
})
return { message: 'Token refreshed successfully', success: true }

View File

@@ -9,7 +9,7 @@ const handlerBuilder =
async (
request: Request,
args: {
params: Promise<{ slug: string[] }>
params: Promise<{ slug?: string[] }>
},
): Promise<Response> => {
const awaitedConfig = await config

View File

@@ -178,7 +178,9 @@ export const buildCollectionFolderView = async (
permissions?.collections?.[config.folders.slug]?.create
? config.folders.slug
: null,
permissions?.collections?.[collectionSlug]?.create ? collectionSlug : null,
resolvedFolderID && permissions?.collections?.[collectionSlug]?.create
? collectionSlug
: null,
].filter(Boolean),
baseFolderPath: `/collections/${collectionSlug}/${config.folders.slug}`,
breadcrumbs,

View File

@@ -1,11 +1,14 @@
import type { AdminViewServerProps } from 'payload'
import type {
AdminViewServerProps,
SanitizedDocumentPermissions,
SanitizedFieldsPermissions,
} from 'payload'
import { buildFormState } from '@payloadcms/ui/utilities/buildFormState'
import React from 'react'
import { getDocPreferences } from '../Document/getDocPreferences.js'
import { getDocumentData } from '../Document/getDocumentData.js'
import { getDocumentPermissions } from '../Document/getDocumentPermissions.js'
import { CreateFirstUserClient } from './index.client.js'
import './index.scss'
@@ -43,18 +46,27 @@ export async function CreateFirstUserView({ initPageResult }: AdminViewServerPro
user: req.user,
})
// Get permissions
const { docPermissions } = await getDocumentPermissions({
collectionConfig,
data,
req,
})
const baseFields: SanitizedFieldsPermissions = Object.fromEntries(
collectionConfig.fields
.filter((f): f is { name: string } & typeof f => 'name' in f && typeof f.name === 'string')
.map((f) => [f.name, { create: true, read: true, update: true }]),
)
// In create-first-user we should always allow all fields
const docPermissionsForForm: SanitizedDocumentPermissions = {
create: true,
delete: true,
fields: baseFields,
read: true,
readVersions: true,
update: true,
}
// Build initial form state from data
const { state: formState } = await buildFormState({
collectionSlug: collectionConfig.slug,
data,
docPermissions,
docPermissions: docPermissionsForForm,
docPreferences,
locale: locale?.code,
operation: 'create',
@@ -69,7 +81,7 @@ export async function CreateFirstUserView({ initPageResult }: AdminViewServerPro
<h1>{req.t('general:welcome')}</h1>
<p>{req.t('authentication:beginCreateFirstUser')}</p>
<CreateFirstUserClient
docPermissions={docPermissions}
docPermissions={docPermissionsForForm}
docPreferences={docPreferences}
initialState={formState}
loginWithUsername={loginWithUsername}

View File

@@ -208,7 +208,7 @@ export const renderDocument = async ({
globalSlug,
locale: locale?.code,
operation,
readOnly: isTrashedDoc,
readOnly: isTrashedDoc || isLocked,
renderAllFields: true,
req,
schemaPath: collectionSlug || globalSlug,
@@ -333,6 +333,7 @@ export const renderDocument = async ({
}
const documentSlots = renderDocumentSlots({
id,
collectionConfig,
globalConfig,
hasSavePermission,

View File

@@ -1,6 +1,5 @@
import type {
BeforeDocumentControlsServerPropsOnly,
DefaultServerFunctionArgs,
DocumentSlots,
EditMenuItemsServerPropsOnly,
PayloadRequest,
@@ -27,10 +26,11 @@ export const renderDocumentSlots: (args: {
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
hasSavePermission: boolean
id?: number | string
permissions: SanitizedDocumentPermissions
req: PayloadRequest
}) => DocumentSlots = (args) => {
const { collectionConfig, globalConfig, hasSavePermission, req } = args
const { id, collectionConfig, globalConfig, hasSavePermission, req } = args
const components: DocumentSlots = {} as DocumentSlots
@@ -39,6 +39,7 @@ export const renderDocumentSlots: (args: {
const isPreviewEnabled = collectionConfig?.admin?.preview || globalConfig?.admin?.preview
const serverProps: ServerProps = {
id,
i18n: req.i18n,
payload: req.payload,
user: req.user,
@@ -169,10 +170,11 @@ export const renderDocumentSlots: (args: {
return components
}
export const renderDocumentSlotsHandler: ServerFunction<{ collectionSlug: string }> = async (
args,
) => {
const { collectionSlug, req } = args
export const renderDocumentSlotsHandler: ServerFunction<{
collectionSlug: string
id?: number | string
}> = async (args) => {
const { id, collectionSlug, req } = args
const collectionConfig = req.payload.collections[collectionSlug]?.config
@@ -187,6 +189,7 @@ export const renderDocumentSlotsHandler: ServerFunction<{ collectionSlug: string
})
return renderDocumentSlots({
id,
collectionConfig,
hasSavePermission,
permissions: docPermissions,

View File

@@ -15,6 +15,16 @@ import './index.scss'
const baseClass = 'logout'
/**
* This component should **just** be the inactivity route and do nothing with logging the user out.
*
* It currently handles too much, the auth provider should just log the user out and then
* we could remove the useEffect in this file. So instead of the logout button
* being an anchor link, it should be a button that calls `logOut` in the provider.
*
* This view is still useful if cookies attempt to refresh and fail, i.e. the user
* is logged out due to inactivity.
*/
export const LogoutClient: React.FC<{
adminRoute: string
inactivity?: boolean
@@ -26,9 +36,11 @@ export const LogoutClient: React.FC<{
const { startRouteTransition } = useRouteTransition()
const [isLoggedOut, setIsLoggedOut] = React.useState<boolean>(!user)
const isLoggedIn = React.useMemo(() => {
return Boolean(user?.id)
}, [user?.id])
const logOutSuccessRef = React.useRef(false)
const navigatingToLoginRef = React.useRef(false)
const [loginRoute] = React.useState(() =>
formatAdminURL({
@@ -45,26 +57,25 @@ export const LogoutClient: React.FC<{
const router = useRouter()
const handleLogOut = React.useCallback(async () => {
const loggedOut = await logOut()
setIsLoggedOut(loggedOut)
if (!inactivity && loggedOut && !logOutSuccessRef.current) {
if (!inactivity && !navigatingToLoginRef.current) {
navigatingToLoginRef.current = true
await logOut()
toast.success(t('authentication:loggedOutSuccessfully'))
logOutSuccessRef.current = true
startRouteTransition(() => router.push(loginRoute))
return
}
}, [inactivity, logOut, loginRoute, router, startRouteTransition, t])
useEffect(() => {
if (!isLoggedOut) {
if (isLoggedIn) {
void handleLogOut()
} else {
} else if (!navigatingToLoginRef.current) {
navigatingToLoginRef.current = true
startRouteTransition(() => router.push(loginRoute))
}
}, [handleLogOut, isLoggedOut, loginRoute, router, startRouteTransition])
}, [handleLogOut, isLoggedIn, loginRoute, router, startRouteTransition])
if (isLoggedOut && inactivity) {
if (!isLoggedIn && inactivity) {
return (
<div className={`${baseClass}__wrap`}>
<h2>{t('authentication:loggedOutInactivity')}</h2>

Some files were not shown because too many files have changed in this diff Show More