Compare commits

...

440 Commits

Author SHA1 Message Date
Jacob Fletcher
f9bb2202b1 fix test 2025-09-05 10:43:30 -04:00
Jacob Fletcher
4d89a2b747 fix join field relationship table 2025-09-05 10:27:47 -04:00
Jacob Fletcher
e62c8a5661 cleanup 2025-09-05 10:10:45 -04:00
Jacob Fletcher
19fea26447 Merge branch 'main' into feat/list-view-select 2025-09-05 09:51:34 -04:00
Jacob Fletcher
38c346b4c7 reset _community and base tsconfig 2025-09-05 09:45:16 -04:00
Jacob Fletcher
142228e70b add test and rename property again 2025-09-05 09:42:47 -04:00
Jacob Fletcher
ad892a1da4 renames to listSelectActiveColumns 2025-09-04 17:01:51 -04:00
Jacob Fletcher
414e5d9363 add docs 2025-09-04 16:59:50 -04:00
Jacob Fletcher
195accdc00 fix column accessor 2025-09-04 16:30:17 -04:00
Jacob Fletcher
7bb5321a95 perf(ui): opt-in to the select api in the list view 2025-09-04 14:35:37 -04: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
Elliot DeNolf
0688050eb6 chore(release): v3.51.0 [skip ci] 2025-08-13 09:20:13 -04:00
Jessica Rynkar
5a99d8c5f4 fix: upload with no filename gives vague error (#13414)
### What?
Adds validation to the file upload field to ensure a filename is
provided. If the filename is missing, a clear error message is shown to
the user instead of a general error.

### Why?
Currently, attempting to upload a file without a filename results in a
generic error message: `Something went wrong.` This makes it unclear for
users to understand what the issue is.

### How?
The upload field validation has been updated to explicitly check for a
missing filename. If the filename is undefined or null, the error
message `A filename is required` is now shown.

Fixes #13410
2025-08-13 12:26:59 +01:00
Jessica Rynkar
35ca98e70e fix: version view breaks when tab field has function for label (#13415)
### What?

Fixes an issue where using a function as the `label` for a `tabs` field
causes the versions UI to break.

### Why?

The versions UI was not properly resolving function labels on `tab`
fields, leading to a crash when trying to render them.

### How?

Tweaked the logic so that if the label is a function, it gets called
before rendering.

Fixes #13375
2025-08-13 09:02:43 +01:00
Jacob Fletcher
255bba9606 feat(ui): update query presets ux (#13095)
Surfaces query preset controls more prominently. Query presets are
central to the function of the list view, if enabled, but the UI is
easily overlooked. This also sets the stage for future enhancements,
such as pinned presets, etc.

Also improves the usability of the search field by extending the hitbox
of the input fully to the boundaries of the container.

Before:


https://github.com/user-attachments/assets/3203561c-68cc-43f4-8ded-c51b7c8e8f0c

After:


https://github.com/user-attachments/assets/13dce7c9-67d8-471f-a85c-2795938b3e3e

---

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

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-08-12 18:59:06 +00:00
Jacob Fletcher
8173180d1d fix(ui): autosave form state discards local changes (#13438)
Follow-up to #13416. Supersedes #13434.

When autosave is triggered and the user continues to modify fields,
their changes are overridden by the server's value, i.e. the value at
the time the form state request was made. This makes it almost
impossible to edit fields when using a small autosave interval and/or a
slow network.

This is because autosave is now merged into form state, which by default
uses `acceptValues: true`. This does exactly what it sounds like,
accepts all the values from the server—which may be stale if underlying
changes have been made. We ignore these values for onChange events,
because the user is actively making changes. But during form
submissions, we can accept them because the form is disabled while
processing anyway.

This pattern allows us to render "computed values" from the server, i.e.
a field with an `beforeChange` hook that modifies its value.

Autosave, on the other hand, happens in the background _while the form
is still active_. This means changes may have been made since sending
the request. We still need to accept computed values from the server,
but we need to avoid doing this if the user has active changes since the
time of the request.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211027929253429
2025-08-12 14:28:04 -04:00
Patrik
3258e78596 test: group-by reset and navigation tests in trash view (#13401)
### What?
Adds e2e tests that verify group-by functionality within the trash view
of a collection.

### Why?
To ensure group-by behaves correctly when viewing soft-deleted
documents, including:
- Clearing the group-by selection via the reset button.
- Navigating from grouped rows to the trashed document's edit view.

### How?
- Added `should properly clear group-by in trash view` to test the reset
button behavior.
- Added `should properly navigate to trashed doc edit view from group-by
in trash view` to confirm correct linking and routing.
2025-08-12 09:49:47 -07:00
Alessio Gravili
ad2564e5fa fix: ensure scheduling by default only handles default queue, add allQueues config to autoRun (#13395)
By default, `payload.jobs.run` only runs jobs from the `default` queue
(since https://github.com/payloadcms/payload/pull/12799). It exposes an
`allQueues` property to run jobs from all queues.

For handling schedules (`payload.jobs.handleSchedules` and
`config.jobs.autoRun`), this behaves differently - jobs are run from all
queues by default, and no `allQueues` property exists.

This PR adds an `allQueues` property to scheduling, as well as changes
the default behavior to only handle schedules for the `default` queue.
That way, the behavior of running and scheduling jobs matches.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210982048221260
2025-08-12 11:55:17 -04:00
Jarrod Flesch
995f96bc70 feat(plugin-multi-tenant): allow tenant field overrides (#13316)
Allows user to override more of the tenant field config. Now you can
override most of the field config with:

### At the root level
```ts
/**
 * Field configuration for the field added to all tenant enabled collections
 */
tenantField?: RootTenantFieldConfigOverrides
```

### At the collection level
Setting collection level overrides will replace the root level overrides
shown above.

```ts
collections: {
  [key in CollectionSlug]?: {
    // ... rest of the types
    /**
     * Overrides for the tenant field, will override the entire tenantField configuration
     */
    tenantFieldOverrides?: CollectionTenantFieldConfigOverrides
  }
}
```
2025-08-12 11:33:29 -04:00
Paul
306b7f6943 fix(ui): misalignment of nested toggles on document API tab (#13424)
Fixes the weird misalignment of toggles in the API tab


Before:
<img width="799" height="1011" alt="image"
src="https://github.com/user-attachments/assets/5fc9768c-24de-4d89-a1ba-6dd606f76bec"
/>


After:
<img width="815" height="1069" alt="image"
src="https://github.com/user-attachments/assets/253dfdaa-7fca-4d16-b61e-a91474b9d6c9"
/>
2025-08-12 11:28:35 -04:00
Marcus Michaels
72f5763c25 fix(ui): field elements showing over the top of Preview content (#13148) 2025-08-12 11:15:10 -04:00
Luke Bennett
a374aabd8d fix(ui): bulk upload issues (#13413)
### What?
This PR contains a couple of fixes to the bulk upload process:
- Credentials not being passed when fetching, leading to auth issues
- Provide a fallback to crypto.randomUUID which is only available when
using HTTPS or localhost

### Why?
I use [separate admin and API URLs](#12682) and work off a remote dev
server using custom hostnames. These issues may not impact the happy
path of using localhost, but are dealbreakers in this environment.

### Fixes #
_These are issues I found myself, I fixed them rather than raising
issues for somebody else to pick up, but I can create issues to link to
if required._
2025-08-12 11:10:01 -04:00
Jacob Fletcher
2bc9a2def4 fix(templates): only generate slug from title on demand (#12956)
Currently, the slug field is generated from the title field
indefinitely, even after the document has been created and the initial
slug has been assigned. This should only occur on create, however, as it
currently does, or when the user explicitly requests it.

Given that slugs often determine the URL structure of the webpage that
the document corresponds to, they should rarely change after being
published, and when they do, would require HTTP redirects, etc. to do
right in a production environment.

But this is also a problem with Live Preview which relies on a constant
iframe src. If your Live Preview URL includes the slug as a route param,
which is often the case, then changing the slug will result in a broken
connection as the queried document can no longer be found. The current
workaround is to save the document and refresh the page.

Now, the slug is only generated on initial create, or when the user
explicitly clicks the new "generate" button above the slug field. In the
future we can evaluate supporting dynamic Live Preview URLs.

Regenerating this URL on every change would put additional load on the
client as it would have to reestablish connection every time it changes,
but it should be supported still. See #13055.

Discord discussion here:
https://discord.com/channels/967097582721572934/1102950643259424828/1387737976892686346

Related: #10536
2025-08-11 17:12:43 -04:00
Jacob Fletcher
1d81b0c6dd fix(ui): autosave hooks are not reflected in form state (#13416)
Fixes #10515. Needed for #12956.

Hooks run within autosave are not reflected in form state.

Similar to #10268, but for autosave events.

For example, if you are using a computed value, like this:

```ts
[
  // ...
  {
    name: 'title',
    type: 'text',
  },
  {
    name: 'computedTitle',
    type: 'text',
    hooks: {
      beforeChange: [({ data }) => data?.title],
    },
  },
]
```

In the example above, when an autosave event is triggered after changing
the `title` field, we expect the `computedTitle` field to match. But
although this takes place on the database level, the UI does not reflect
this change unless you refresh the page or navigate back and forth.

Here's an example:

Before:


https://github.com/user-attachments/assets/c8c68a78-9957-45a8-a710-84d954d15bcc

After:


https://github.com/user-attachments/assets/16cb87a5-83ca-4891-b01f-f5c4b0a34362

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210561273449855
2025-08-11 16:59:03 -04:00
German Jablonski
9c8f3202e4 fix(richtext-lexical, plugin-multi-tenant): text editor exposes documents from other tenants (#13229)
## What

Before this PR, an internal link in the Lexical editor could reference a
document from a different tenant than the active one.

Reproduction:
1. `pnpm dev plugin-multi-tenant`
2. Log in with `dev@payloadcms.com` and password `test`
3. Go to `http://localhost:3000/admin/collections/food-items` and switch
between the `Blue Dog` and `Steel Cat` tenants to see which food items
each tenant has.
4. Go to http://localhost:3000/admin/collections/food-items/create, and
in the new richtext field enter an internal link
5. In the relationship select menu, you will see the 6 food items at
once (3 of each of those tenants). In the relationship select menu, you
would previously see all 6 food items at once (3 from each of those
tenants). Now, you'll only see the 3 from the active tenant.

The new test verifies that this is fixed.

## How

`baseListFilter` is used, but now it's called `baseFilter` for obvious
reasons: it doesn't just filter the List View. Having two different
properties where the same function was supposed to be placed wasn't
feasible. `baseListFilter` is still supported for backwards
compatibility. It's used as a fallback if `baseFilter` isn't defined,
and it's documented as deprecated.

`baseFilter` is injected into `filterOptions` of the internal link field
in the Lexical Editor.
2025-08-07 11:24:15 -04:00
Rodrigo Antunes
161769e50c chore(templates): fix typo in e2e tests (headging -> heading) (#13391)
### What?

Small typo in templates e2e tests: `headging`
2025-08-07 15:10:45 +00:00
Sasha
c9a1590fc4 fix(ui): search in select fields with filterOptions (#13397)
Fixes https://github.com/payloadcms/payload/issues/13236
2025-08-07 09:57:56 -04:00
Alessio Gravili
e870be094e docs: add dependency troubleshooting docs (#13385)
Dependency mismanagement is the #1 cause of issues people have with
payload. This PR adds a details docs section explaining why those issues
occur and how to fix them.

**[👀 Click here for docs
preview](https://payloadcms.com/docs/dynamic/troubleshooting/troubleshooting?branch=docs/dependencies)**
2025-08-06 11:19:32 -04:00
Jacob Fletcher
d4f198651c fix(next): group by boolean values (#13382)
When grouping by a checkbox field, boolean values are not translated,
causing labels to render incorrectly, and falsey values to render
without a heading.


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210979856538211
2025-08-05 20:07:29 -04:00
Yi Chi
5d8f8dc0a5 fix(translations): update traditional chinese (zh-TW) localization (#13370)
A comprehensive revision has been made to correct the majority of
localization translation errors. Previous versions were often
grammatically incorrect and awkward. This update includes a one-time
overhaul to improve grammar, vocabulary, and fix a few terms that were
written in Simplified Chinese.

Since a large number of translated terms have been corrected, it is
recommended to use GitHub's Files changed feature to review the diffs
directly.

This Pull Request only modifies translation content; no other code
changes have been made.


<!--

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 #

-->
2025-08-05 20:12:58 +00:00
Jacob Fletcher
7344d64be3 fix(next): group by dates with null values (#13381)
When grouping by a date field and its value is null, the list view
crashes.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210979856538208
2025-08-05 15:54:55 -04:00
Sam
2211f3dd1c fix: use thumbnailUrl for upload documents in folder view (#13368)
### What?
Fix the folder view for upload documents only using
`formatFolderOrDocumentItem()` function and only if the upload is an
image, even when there's a `thumbnailURL` available.

### Why?
Folder view for upload collections (especially those with sharp resizing
disabled) renders different thumbnails between the folder view and list
view. With sharp resizing disabled and an `adminThumbnail` fn provided,
the list view will correctly render optimised images, while the folder
view renders full source images - resulting in a huge discrepancy in
loaded image sizes.

### How?
We're passing the `value.thumbnailURL` **before** the
`formatFolderOrDocumentItem()` call rather than passing it directly as a
function parameter to cover cases where non-image uploads have a
`thumbnailURL` defined.

Fixes #13246
2025-08-05 13:39:59 -04:00
Elliot DeNolf
ac40185158 ci: update release-commenter permissions (#13373)
Permissions for release commenter needed to be explicit.

Docs from upstream:
https://github.com/apexskier/github-release-commenter/pull/549
2025-08-05 10:00:42 -04:00
Elliot DeNolf
d622d3c5e7 chore(release): v3.50.0 [skip ci] 2025-08-05 09:33:56 -04:00
Jessica Rynkar
b74f4fb9b2 fix(ui): fallback to default locale checkbox passes wrong value (#12396)
### What?
Allows document to successfully be saved when `fallback to default
locale` checked without throwing an error.

### Why?
The `fallback to default locale` checkbox allows users to successfully
save a document in the admin panel while using fallback data for
required fields, this has been broken since the release of `v3`.

Without the checkbox override, the user would be prevented from saving
the document in the UI because the field is required and will throw an
error.

The logic of using fallback data is not affected by this checkbox - it
is purely to allow saving the document in the UI.

### How?
The `fallback` checkbox used to have an `onChange` function that
replaces the field value with null, allowing it to get processed through
the standard localization logic and get replaced by fallback data.
However, this `onChange` was removed at some point and the field was
passing the actual checkbox value `true`/`false` which then breaks the
form and prevent it from saving.

This fallback checkbox is only displayed when `fallback: true` is set in
the localization config.
This PR also updated the checkbox to only be displayed when `required:
true` - when it's the field is not `required` this checkbox serves no
purpose.

Also adds tests to `localization/e2e`.

Fixes #11245

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-08-05 09:29:10 -04:00
Patrik
8401b2166d fix: deletedAt in TypeWithID not accepting null from generated types (#13363)
### What?

Updated `TypeWithID` so `deletedAt` can accept `null`.

### Why?

Generated collection types for trash use:

```
deletedAt?: string | null
```

`TypeWithID` previously only allowed `string | undefined`, so assigning
documents with `deletedAt: null` caused TypeScript errors.

Aligning the types fixes this mismatch and ensures `TypeWithID` is
compatible with the generated types.

### How?

Modified the `TypeWithID` definition to:

```
export type TypeWithID = {
  deletedAt?: string | null
  docId?: any
  id: number | string
}
```

This makes `deletedAt` effectively `string | null | undefined`, matching
generated types and eliminating type errors.

Fixes #13341
2025-08-05 09:01:01 -04:00
Jarrod Flesch
20b4de94ee fix(plugin-multi-tenant): constrain results to assigned tenants when present (#13365)
Extension of https://github.com/payloadcms/payload/pull/13213

This PR correctly filters tenants, users and documents based on the
users assigned tenants if any are set. If a user is assigned tenants
then list results should only show documents with those tenants (when
selector is not set). Previously you could construct access results that
allows them to see them, but in the confines of the admin panel they
should not see them. If you wanted a user to be able to see a "public"
tenant while inside the admin panel they either need to be added to the
tenant or have no tenants at all.

Note that this is for filtering only, access control still controls what
documents a user has _access_ to a document. The filters are and always
have been a way to filter out results in the list view.
2025-08-05 09:00:36 -04:00
Jarrod Flesch
43b4b22af9 fix: svg uploads allowed when glob (#13356)
Builds on top of https://github.com/payloadcms/payload/pull/13276 and
allows images/* to also parse and accept svg files correctly.
2025-08-04 23:47:37 -04:00
Jarrod Flesch
3b9dba8641 chore: adds session helper exports (#13367)
Creates/exports `addSessionToUser` helper and also exports the
`removeExpiredSessions` helper.
2025-08-04 22:53:36 -04:00
Alessio Gravili
1d70d4d36c fix(next): version view did not properly handle field-level permissions (#13336)
Field-level permissions were not handled correctly at all. If you had a
field set with access control, this would mean that nested fields would
incorrectly be omitted from the version view.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210932060696919
2025-08-02 12:01:48 -07:00
Jens Becker
1b31c74d32 perf(ui): only select the useAsTitle field when fetching the document data of a relationship value (#13228)
### What?
Optimize the relationship value loading by selecting only the
`useAsTitle` field when fetching document data via the REST API.

### Why?
Previously, all fields were fetched via a POST request when loading the
document data of a relationship value, causing unnecessary data transfer
and slower performance. Only the `useAsTitle` field is needed to display
the related document’s title in the relationship UI field.

### How?
Applied a select to the REST API POST request, similar to how the
options list is loaded, limiting the response to the `useAsTitle` field
only.
2025-08-01 16:18:21 -04:00
Sasha
f432cc1956 feat(graphql): allow to pass count: true to a join query (#13351)
Fixes https://github.com/payloadcms/payload/issues/13077
2025-08-01 18:05:54 +03:00
Jarrod Flesch
2903486974 fix(ui): group/array error paths persisting when valid (#13347)
Fields such as groups and arrays would not always reset errorPaths when
there were no more errors. The server and client state was not being
merged safely and the client state was always persisting when the server
sent back no errorPaths, i.e. itterable fields with fully valid
children. This change ensures errorPaths is defaulted to an empty array
if it is not present on the incoming field.

Likely a regression from
https://github.com/payloadcms/payload/pull/9388.

Adds e2e test.
2025-08-01 16:04:51 +01:00
Jarrod Flesch
b965db881e fix(ui): add hidden sidebar fields css specificity (#13344)
Adds specificity to sidebar no fields css styling.
2025-07-31 08:48:38 -04:00
Jarrod Flesch
1b93c4becc fix(ui): hide sidebar when no fields rendered (#13340) 2025-07-30 23:59:46 -04:00
Jarrod Flesch
9031f3bf23 feat: add hooks to restoreVersion collection operation (#13333)
Adds missing hooks to the restoreVersion operation.
- beforeOperation
- beforeValidate - Fields
- beforeValidate - Collection
- beforeChange - Collection
- beforeChange - Fields
- afterOperation
2025-07-30 21:25:53 -04:00
Muhammad Nizar
df91321f4a feat(translations): add Indonesian translations (#13323)
### What?
Translated payload to Indonesian

### Why?
I needed to have payload in Indonesian

### How?
By following the
[readme](https://github.com/payloadcms/payload/blob/main/packages/translations/README.md)
2025-07-30 21:35:22 +00:00
Patrik
11755089f8 feat: adds trash support to the count operation (#13304)
### What?

- Updated the `countOperation` to respect the `trash` argument.

### Why?

- Previously, `count` would incorrectly include trashed documents even
when `trash` was not specified.
- This change aligns `count` behavior with `find` and other operations,
providing accurate counts for normal and trashed documents.

### How?

- Applied `appendNonTrashedFilter` in `countOperation` to automatically
exclude soft-deleted docs when `trash: false` (default).
- Added `trash` argument support in Local API, REST API (`/count`
endpoints), and GraphQL (`count<Collection>` queries).
2025-07-30 14:11:11 -07:00
Patrik
a8b6983ab5 test: adds e2e tests for auth enabled collections with trash enabled (#13317)
### What?
- Added new end-to-end tests covering trash functionality for
auth-enabled collections (e.g., `users`).
- Implemented test cases for:
  - Display of the trash tab in the list view.
  - Trashing a user and verifying its appearance in the trash view.
  - Accessing the trashed user edit view.
  - Ensuring all auth fields are properly disabled in trashed state.
  - Restoring a trashed user and verifying its status.

### Why?
- To ensure that the trash (soft-delete) feature works consistently for
collections with `auth: true`.
- To prevent regressions in user management flows, especially around
disabling and restoring trashed users.

### How?
- Added a new `Auth enabled collection` test suite in the E2E `Trash`
tests.
2025-07-30 14:11:02 -07:00
Jarrod Flesch
f2d4004237 fix(ui): incorrect padding when using rtl (#13338)
Fixes inconsistent padding in the doc view for RTL viewing.

###  Before

#### Desktop
<img width="1331" height="310" alt="CleanShot 2025-07-30 at 16 37 30"
src="https://github.com/user-attachments/assets/48d3e127-09dd-4356-99ae-16fe47817937"
/>

#### Mobile
<img width="619" height="328" alt="CleanShot 2025-07-30 at 16 37 52"
src="https://github.com/user-attachments/assets/36169ca5-c1a2-4175-a6e1-f0a4784d5e9e"
/>


###  After

#### Desktop
<img width="1675" height="291" alt="CleanShot 2025-07-30 at 16 39 18"
src="https://github.com/user-attachments/assets/1da78a8a-b236-4f95-9eb2-8b5055b676ae"
/>

#### Mobile
<img width="531" height="309" alt="CleanShot 2025-07-30 at 16 39 30"
src="https://github.com/user-attachments/assets/af858bfc-6d75-4139-ada1-4f8100744bfb"
/>
2025-07-30 21:03:03 +00:00
Alessio Gravili
8a489410ad fix(next): incorrect version view stepnav value (#13305)
Previously, the version view stepnav incorrectly displayed the version
ID instead of the parent document ID in the navigation. It also
incorrectly pulled the field value if `useAsTitle` was set, displayed
the `[undefined]` instead.

**Edit View:**
<img width="2218" height="244" alt="Screenshot 2025-07-28 at 16 55
26@2x"
src="https://github.com/user-attachments/assets/7b144480-ec5a-4592-b603-09e1e35bd558"
/>

## Version View - Before:

<img width="2280" height="378" alt="Screenshot 2025-07-28 at 16 56
02@2x"
src="https://github.com/user-attachments/assets/8b79bab3-a79b-4930-ade1-2da450f00fc7"
/>


## Version View - After:

**Version View:**
<img width="2222" height="358" alt="Screenshot 2025-07-28 at 16 55
46@2x"
src="https://github.com/user-attachments/assets/bb0fffbb-c3e6-419f-ad72-5731e85059cc"
/>


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210907527783756
2025-07-30 20:59:48 +00:00
Alessio Gravili
095e7d904f fix(next): safely access relationTo value from relationship field (#13337)
Fixes https://github.com/payloadcms/payload/issues/13330
2025-07-30 20:54:04 +00:00
Patrik
c48b57fdbf fix(next): incorrect doc link in trash view with groupBy enabled (#13332)
### What?

Fixes an issue where document links in the trash view were incorrectly
generated when group-by was enabled for a collection. Previously, links
in grouped tables would omit the `/trash` segment, causing navigation to
crash while trying to route to the default edit view instead of the
trashed document view.

### Why?

When viewing a collection in group-by mode, document rows are rendered
in grouped tables via the `handleGroupBy` logic. However, these tables
were unaware of whether the view was operating in trash mode, so the
generated row links did not include the necessary `/trash` segment. This
broke navigation when trying to view or edit trashed documents.

### How?

- Threaded the `viewType` prop through `renderListView` into the
`handleGroupBy` utility.
- Passed `viewType` into each `renderTable` call within `handleGroupBy`,
ensuring proper link generation.
- `renderTable` already supports `viewType` and appends `/trash` to edit
links when it's set to 'trash'.
2025-07-30 13:17:03 -07:00
Sasha
b26a73be4a fix: querying hasMany: true select fields inside polymorphic joins (#13334)
This PR fixes queries like this:

```ts
const findFolder = await payload.find({
  collection: 'payload-folders',
  where: {
    id: {
      equals: folderDoc.id,
    },
  },
  joins: {
    documentsAndFolders: {
      limit: 100_000,
      sort: 'name',
      where: {
        and: [
          {
            relationTo: {
              equals: 'payload-folders',
            },
          },
          {
            folderType: {
              in: ['folderPoly1'], // previously this didn't work
            },
          },
        ],
      },
    },
  },
})
```

Additionally, this PR potentially fixes querying JSON fields by the top
level path, for example if your JSON field has a value like: `[1, 2]`,
previously `where: { json: { equals: 1 } }` didn't work, however with a
value like `{ nested: [1, 2] }` and a query `where: { 'json.nested': {
equals: 1 } }`it did.

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-07-30 15:30:20 -04:00
Alessio Gravili
3114b89d4c perf: 23% faster job queue system on postgres/sqlite (#13187)
Previously, a single run of the simplest job queue workflow (1 single
task, no db calls by user code in the task - we're just testing db
system overhead) would result in **22 db roundtrips** on drizzle. This
PR reduces it to **17 db roundtrips** by doing the following:

- Modifies db.updateJobs to use the new optimized upsertRow function if
the update is simple
- Do not unnecessarily pass the job log to the final job update when the
workflow completes => allows using the optimized upsertRow function, as
only the main table is involved

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210888186878606
2025-07-30 16:23:43 +03:00
Evelyn Hathaway
227a20e94b fix(richtext-lexical): recursively unwrap generic Slate nodes in Lexical migration converter (#13202)
## What?

The Slate to Lexical migration script assumes that the depth of Slate
nodes matches the depth of the Lexical schema, which isn't necessarily
true. This pull request fixes this assumption by first checking for
children and unwrapping the text nodes.

## Why?

During my migration, I ran into a lot of copy + pasted rich text with
list items with untyped nodes with `children`. The existing migration
script assumed that since list items can't have paragraphs, all untyped
nodes inside must be text nodes.

The result of the migration script was a lot of invalid text nodes with
`text: undefined` and all of the content in the `children` being
silently lost. Beyond the silent loss, the invalid text nodes caused the
Lexical editor to unmount with an error about accessing `0 of
undefined`, so those documents couldn't be edited.

This additionally makes the migration script more closely align with the
[recursive serialization logic recommendation from the Payload Slate
Rich Text
documentation](https://payloadcms.com/docs/rich-text/slate#generating-html).

## Visualization

### Slate

```txt
Slate rich text content
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┗━┳━ Generic (paragraph-like, untyped with children)
┋ ┋   ┣━━━ Text (untyped) `Hello `
┋ ┋   ┗━━━ Text (untyped) `World!
[...]
```

### Lexical Before PR

```txt
Lexical rich text content (invalid)
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┗━━━ Invalid text (assumed the generic node was text, stopped processing children, cannot restore lost text without a restoring backup with Slate and rerunning the script after this MR)
[...]
```

### Lexical After PR

```txt
Lexical rich text content
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┣━━━ Text `Hello `
┋ ┋ ┗━━━ Text `World!
[...]
```

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-07-30 13:16:18 +00:00
Jarrod Flesch
a22f27de1c test: stabilize frequent fails (#13318)
Adjusts tests that "flake" frequently.
2025-07-30 05:52:01 -07:00
Patrik
e7124f6176 fix(next): cannot filter trash (#13320)
### What?

- Updated `TrashView` to pass `trash: true` as a dedicated prop instead
of embedding it in the `query` object.
- Modified `renderListView` to correctly merge `trash` and `where`
queries by using both `queryFromArgs` and `queryFromReq`.
- Ensured filtering (via `where`) works correctly in the trash view.

### Why?

Previously, the `trash: true` flag was injected into the `query` object,
and `renderListView` only used `queryFromArgs`.
This caused the `where` clause from filters (added by the
`WhereBuilder`) to be overridden, breaking filtering in the trash view.

### How?

- Introduced an explicit `trash` prop in `renderListView` arguments.
- Updated `TrashView` to pass `trash: true` separately.
- Updated `renderListView` to apply the `trash` filter in addition to
any `where` conditions.
2025-07-29 14:29:04 -07:00
Elliot DeNolf
183f313387 chore(release): v3.49.1 [skip ci] 2025-07-29 16:38:50 -04:00
contip
b1fa76e397 fix: keep apiKey encrypted in refresh operation (#13063) (#13177)
### What?
Prevents decrypted apiKey from being saved back to database on the auth
refresh operation.

### Why?
References issue #13063: refreshing a token for a logged-in user
decrypted `apiKey` and wrote it back in plaintext, corrupting the user
record.

### How?
The user is now fetched with `db.findOne` instead of `findByID`,
preserving the encryption of the key when saved back to the database
using `db.updateOne`. The user record is then re-fetched using
`findByID`, allowing for the decrypted key to be provided in the
response.

### Tests
*  keeps apiKey encrypted in DB after refresh
*  returns user with decrypted apiKey after refresh

Fixes #13063
2025-07-29 16:27:45 -04:00
German Jablonski
08942494e3 fix: filters cookies with the payload- prefix in getExternalFile by default (#13215)
### What

- filters cookies with the `payload-` prefix in `getExternalFile` by
default (if `externalFileHeaderFilter` is not used).
- Document in `externalFileHeaderFilter`, that the user should handle
the removing of the payload cookie.

### Why

In the Payload application, the `getExternalFile` function sends the
user's cookies to an external server when fetching media, inadvertently
exposing the user's session to that third-party service.




```ts
const headers = uploadConfig.externalFileHeaderFilter
  ? uploadConfig.externalFileHeaderFilter(Object.fromEntries(new Headers(req.headers)))
  : { cookie: req.headers?.get('cookie') };

const res = await fetch(fileURL, {
  credentials: 'include',
  headers,
  method: 'GET',
});
```
Although the
[externalFileHeaderFilter](https://payloadcms.com/docs/upload/overview#collection-upload-options)
function can strip sensitive cookies from the request, the default
config includes the session cookie, violating the secure-by-default
principle.

### How

- If `externalFileHeaderFilter` is not defined, any cookie beginning
with `payload-` is filtered.
- Added 2 tests: both for the case where `externalFileHeaderFilter` is
defined and for the case where it is not.





---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210561338171125
2025-07-29 16:21:50 -04:00
Adebayo Ajayi
da8bf69054 fix(translations): improve swedish translations (#13272)
### What?
Update Swedish translation, removing minor inconsistencies and opting
for more natural sounding translations
### Why?
The current Swedish translation contained some minor grammatical issues
and inconsistencies that make the UI feel less natural to Swedish users.
### How?
- Fixed "e-post" hyphenation consistency
- Changed "Alla platser" → "Alla språk" (locales should be "languages")
- Improved action verbs: "Tydlig" → "Rensa", "Stänga" → "Stäng"
- Made "Kollapsa" → "Fäll ihop" more natural
- Standardized preview terminology: "Live förhandsgranskning" →
"förhandsgranskning"
- Fixed terminology: "fältdatabas" → "fältdata" (fältdatabas mean field
database while fältdata means field data)
- Changed "Programinställningar" → "Systeminställningar" (more
appropriate for software)
- Fixed punctuation: em dash → comma in "sorryNotFound"
- Improved "Visa endast läsning" → "Visa som skrivskyddad"
(grammatically correct)

Fixes #
2025-07-29 18:37:12 +00:00
brutumfulmen97
26d9daeccf fix(translations): missing closing brace in rs latin translation for lastSavedAgo (#13172)
Fixed missing closing brace in the translations package for the language
rs latin.
<img width="885" height="200" alt="image"
src="https://github.com/user-attachments/assets/12d00305-6cc9-46ce-87e8-2c66f9d9e63c"
/>
Here is the code diff.

Co-authored-by: Vlatko Percic <vlatko@studiopresent.com>
2025-07-29 18:20:29 +00:00
Alejandro Martinez
fc5944840e docs: remove asterisk from optional url property in live preview docs (#13108)
### What?

url option is not required for Link Preview, so I removed the asterisk.

### Why?

Here is the type definition

[Type Definition
Link](c6105f1e0d/packages/payload/src/config/types.ts (L159))
2025-07-29 14:16:19 -04:00
Aayush Rajagopalan
9e04dbb1ca docs: typo in vercel-content-link.mdx (#13170)
### Fixes:
'title,' -> 'title',
2025-07-29 18:07:15 +00:00
Sean Zubrickas
72954ce9f2 docs: fixes typo in ternary operator for live preview docs (#13163)
Fixes ternary operator in live preview docs.
2025-07-29 13:56:31 -04:00
Jacob Fletcher
e50220374e fix(next): group by null relationship crashes list view (#13315)
When grouping by a relationship field and it's value is `null`, the list
view crashes.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210916642997992
2025-07-29 11:55:02 -04:00
Jacob Fletcher
61ee8fadca fix(ui): autosave-enabled document drawers close unexpectedly within the join field (#13298)
Fixes #12975.

When editing autosave-enabled documents through the join field, the
document drawer closes unexpectedly on every autosave interval, making
it nearly impossible to use.

This is because as of #12842, the underlying relationship table
re-renders on every autosave event, remounting the drawer each time. The
fix is to lift the drawer out of table's rendering tree and into the
join field itself. This way all rows share the same drawer, whose
rendering lifecycle has been completely decoupled from the table's
state.

Note: this is very similar to how relationship fields achieve similar
functionality.

This PR also adds jsdocs to the `useDocumentDrawer` hook and strengthens
its types.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210906078627353
2025-07-29 11:49:15 -04:00
Jarrod Flesch
8d84352ee9 fix(next): catch list filter errors, prevent list view crash (#13297)
Catches list filter errors and prevents the list view from crashing when
attempting to search on fields the user does not have access to. Instead
just shows the default "no results found" message.
2025-07-29 11:30:07 -04:00
Elliot DeNolf
4beb27b9ad ci: show path value in audit-dependencies script [skip ci] (#13314)
Improve audit-dependencies script to show the vulnerable package path:

```diff
   {
     "package": "form-data",
     "vulnerable": "<2.5.4",
-    "fixed_in": ">=2.5.4"
+    "fixed_in": ">=2.5.4",
+    "findings": [
+      {
+        "version": "2.5.2",
+        "paths": [
+          "packages/storage-gcs > @google-cloud/storage@7.14.0 > retry-request@7.0.2 > @types/request@2.48.12 > form-data@2.5.2"
+        ]
+      }
+    ]
   }
 ]
```
2025-07-29 11:08:39 -04:00
Jacob Fletcher
c5c8c13057 fix(next): pass req through document tab conditions and custom server components (#13302)
Custom document tab components (server components) do not receive the
`user` prop, as the types suggest. This makes it difficult to wire up
conditional rendering based on the user. This is because tab conditions
don't receive a user argument either, forcing you to render the default
tab component yourself—but a custom component should not be needed for
this in the first place.

Now they both receive `req` alongside `user`, which is more closely
aligned with custom field components.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210906078627357
2025-07-28 23:35:38 -04:00
Jacob Fletcher
a888d5cc53 chore(ui): var name typo in relationship field (#13295)
Fixes typo in variable name within the relationship field component.

`disableFormModication` → `disableFormModification`
2025-07-28 23:34:32 -04:00
Jarrod Flesch
72349245ca test: fix flaky sorting test (#13303)
Ensures the browser uses fresh data after seeding by refreshing the
route and navigating when done.
2025-07-29 03:25:09 +00:00
Alessio Gravili
4fde0f23ce fix: use atomic operation for incrementing login attempts (#13204)
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210561338171141
2025-07-28 16:08:10 -07:00
Patrik
aff2ce1b9b fix(next): unable to view trashed documents when group-by is enabled (#13300)
### What?

- Fixed an issue where group-by enabled collections with `trash: true`
were not showing trashed documents in the collection’s trash view.
- Ensured that the `trash` query argument is properly passed to the
`findDistinct` call within `handleGroupBy`, allowing trashed documents
to be included in grouped list views.

### Why?

Previously, when viewing the trash view of a collection with both
**group-by** and **trash** enabled, trashed documents would not appear.
This was caused by the `trash` argument not being forwarded to
`findDistinct` in `handleGroupBy`, which resulted in empty or incorrect
group-by results.

### How?

- Passed the `trash` flag through all relevant `findDistinct` and `find`
calls in `handleGroupBy`.
2025-07-28 11:29:04 -07:00
Alessio Gravili
5c94d2dc71 feat: support next.js 15.4.4 (#13280)
- bumps next.js from 15.3.2 to 15.4.4 in monorepo and templates. It's
important to run our tests against the latest Next.js version to
guarantee full compatibility.
- bumps playwright because of peer dependency conflict with next 15.4.4
- bumps react types because why not

https://nextjs.org/blog/next-15-4

As part of this upgrade, the functionality added by
https://github.com/payloadcms/payload/pull/11658 broke. This PR fixes it
by creating a wrapper around `React.isValidElemen`t that works for
Next.js 15.4.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210803039809808
2025-07-28 16:23:43 +00:00
Jarrod Flesch
b1aac19668 chore(next): cleanup unused code (#13292)
Looks like a merge resolution kept unused code. The same condition is
added a couple lines below this removal.
2025-07-28 13:43:51 +00:00
Sean Zubrickas
d093bb1f00 fix: refactors toast error rendering (#13252)
Fixes #13191

- Render a single html element for single error messages
- Preserve ul structure for multiple errors
- Updates tests to check for both cases
2025-07-28 05:59:25 -07:00
Alessio Gravili
2e9ba10fb5 docs: remove obsolete scheduler property (#13278)
That property does not exist and was used in a previous, outdated
implementation of auto scheduling
2025-07-25 16:25:47 -07:00
Alessio Gravili
8518141a5e fix(drizzle): respect join.type config (#13258)
Respects join.type instead of hardcoding leftJoin
2025-07-25 15:46:20 -07:00
Alessio Gravili
6d6c9ebc56 perf(drizzle): 2x faster db.deleteMany (#13255)
Previously, `db.deleteMany` on postgres resulted in 2 roundtrips to the
database (find + delete with ids). This PR passes the where query
directly to the `deleteWhere` function, resulting in only one roundtrip
to the database (delete with where).

If the where query queries other tables (=> joins required), this falls
back to find + delete with ids. However, this is also more optimized
than before, as we now pass `select: { id: true }` to the findMany
query.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210871676349299
2025-07-25 15:46:09 -07:00
German Jablonski
7cd4a8a602 fix(richtext-lexical): unify indent between different converters and make paragraphs and lists match without CSS (#13274)
Previously, the Lexical editor was using px, and the JSX converter was
using rem. #12848 fixed the inconsistency by changing the editor to rem,
but it should have been the other way around, changing the JSX converter
to px.

You can see the latest explanation about why it should be 40px
[here](https://github.com/payloadcms/payload/issues/13130#issuecomment-3058348085).
In short, that's the default indentation all browsers use for lists.

This time I'm making sure to leave clear comments everywhere and a test
to avoid another regression.

Here is an image of what the e2e test looks like:

<img width="321" height="678" alt="image"
src="https://github.com/user-attachments/assets/8880c7cb-a954-4487-8377-aee17c06754c"
/>

The first part is the Lexical editor, the second is the JSX converter.

As you can see, the checkbox in JSX looks a little odd because it uses
an input checkbox (as opposed to a pseudo-element in the Lexical
editor). I thought about adding an inline style to move it slightly to
the left, but I found that browsers don't have a standard size for the
checkbox; it varies by browser and device.
That requires a little more thought; I'll address that in a future PR.

Fixes #13130
2025-07-25 22:58:49 +01:00
Jarrod Flesch
bc802846c5 fix: serve svg+xml as svg (#13277)
Based from https://github.com/payloadcms/payload/pull/13276

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

If an uploaded image has `.svg` ext, and the mimeType is read as
`application/xml` adjust the mimeType to `image/svg+xml`.

---------

Co-authored-by: Philipp Schneider <47689073+philipp-tailor@users.noreply.github.com>
2025-07-25 21:00:51 +00:00
Jarrod Flesch
e8f6cb5ed1 fix: svg+xml file detection (#13276)
Adds logic for svg+xml file type detection.

---------

Co-authored-by: Philipp Schneider <47689073+philipp-tailor@users.noreply.github.com>
2025-07-25 18:33:53 +00:00
Elliot DeNolf
23bd67515c templates: bump for v3.49.0 (#13273)
🤖 Automated bump of templates for v3.49.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-25 13:39:09 -04:00
Jarrod Flesch
e29d1d98d4 fix(plugin-multi-tenant): prefer assigned tenants for selector population (#13213)
When populating the selector it should populate it with assigned tenants
before fetching all tenants that a user has access to.

You may have "public" tenants and while a user may have _access_ to the
tenant, the selector should show the ones they are assigned to. Users
with full access are the ones that should be able to see the public ones
for editing.
2025-07-25 10:10:26 -04:00
Elliot DeNolf
4ac428d250 chore(release): v3.49.0 [skip ci] 2025-07-25 09:27:41 -04:00
Sasha
75385de01f fix: filtering by polymorphic relationships inside other fields (#13265)
Previously, filtering by a polymorphic relationship inside an array /
group (unless the `name` is `version`) / tab caused `QueryError: The
following path cannot be queried:`.
2025-07-25 09:10:21 -04:00
Patrik
f63dc2a10c feat: adds trash support (soft deletes) (#12656)
### What?

This PR introduces complete trash (soft-delete) support. When a
collection is configured with `trash: true`, documents can now be
soft-deleted and restored via both the API and the admin panel.

```
import type { CollectionConfig } from 'payload'

const Posts: CollectionConfig = {
  slug: 'posts',
  trash: true, // <-- New collection config prop @default false
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    // other fields...
  ],
}
```

### Why

Soft deletes allow developers and admins to safely remove documents
without losing data immediately. This enables workflows like reversible
deletions, trash views, and auditing—while preserving compatibility with
drafts, autosave, and version history.

### How?

#### Backend

- Adds new `trash: true` config option to collections.
- When enabled:
  - A `deletedAt` timestamp is conditionally injected into the schema.
- Soft deletion is performed by setting `deletedAt` instead of removing
the document from the database.
- Extends all relevant API operations (`find`, `findByID`, `update`,
`delete`, `versions`, etc.) to support a new `trash` param:
  - `trash: false` → excludes trashed documents (default)
  - `trash: true` → includes both trashed and non-trashed documents
- To query **only trashed** documents: use `trash: true` with a `where`
clause like `{ deletedAt: { exists: true } }`
- Enforces delete access control before allowing a soft delete via
update or updateByID.
- Disables version restoring on trashed documents (must be restored
first).

#### Admin Panel

- Adds a dedicated **Trash view**: `/collections/:collectionSlug/trash`
- Default delete action now soft-deletes documents when `trash: true` is
set.
- **Delete confirmation modal** includes a checkbox to permanently
delete instead.
- Trashed documents:
- Displays UI banner for better clarity of trashed document edit view vs
non-trashed document edit view
  - Render in a read-only edit view
  - Still allow access to **Preview**, **API**, and **Versions** tabs
- Updated Status component:
- Displays “Previously published” or “Previously a draft” for trashed
documents.
  - Disables status-changing actions when documents are in trash.
- Adds new **Restore** bulk action to clear the `deletedAt` timestamp.
- New `Restore` and `Permanently Delete` buttons for
single-trashed-document restore and permanent deletion.
- **Restore confirmation modal** includes a checkbox to restore as
`published`, defaults to `draft`.
- Adds **Empty Trash** and **Delete permanently** bulk actions.
  
#### Notes

- This feature is completely opt-in. Collections without trash: true
behave exactly as before.



https://github.com/user-attachments/assets/00b83f8a-0442-441e-a89e-d5dc1f49dd37
2025-07-25 09:08:22 -04:00
German Jablonski
4a712b3483 fix(ui): preserve localized blocks and arrays when using CopyToLocale (#13216)
## Problem:
In PR #11887, a bug fix for `copyToLocale` was introduced to address
issues with copying content between locales in Postgres. However, an
incorrect algorithm was used, which removed all "id" properties from
documents being copied. This led to bug #12536, where `copyToLocale`
would mistakenly delete the document in the source language, affecting
not only Postgres but any database.

## Cause and Solution:

When copying documents with localized arrays or blocks, Postgres throws
errors if there are two blocks with the same ID. This is why PR #11887
removed all IDs from the document to avoid conflicts. However, this
removal was too broad and caused issues in cases where it was
unnecessary.


The correct solution should remove the IDs only in nested fields whose
ancestors are localized. The reasoning is as follows:
- When an array/block is **not localized** (`localized: false`), if it
contains localized fields, these fields share the same ID across
different locales.
- When an array/block **is localized** (`localized: true`), its
descendant fields cannot share the same ID across different locales if
Postgres is being used. This wouldn't be an issue if the table
containing localized blocks had a composite primary key of `locale +
id`. However, since the primary key is just `id`, we need to assign a
new ID for these fields.

This PR properly removes IDs **only for nested fields** whose ancestors
are localized.

Fixes #12536

## Example:
### Before Fix:
```js
// Original document (en)
array: [{
  id: "123",
  text: { en: "English text" }
}]

// After copying to 'es' locale, a new ID was created instead of updating the existing item
array: [{
  id: "456",  // 🐛 New ID created!
  text: { es: "Spanish text" } // 🐛 'en' locale is missing
}]
```
### After fix:
```js
// After fix
array: [{
  id: "123",  //  Same ID maintained
  text: {
    en: "English text",
    es: "Spanish text"  //  Properly merged with existing item
  }
}]
```


## Additional fixes:

### TraverseFields

In the process of designing an appropriate solution, I detected a couple
of bugs in traverseFields that are also addressed in this PR.

### Fixed MongoDB Empty Array Handling

During testing, I discovered that MongoDB and PostgreSQL behave
differently when querying documents that don't exist in a specific
locale:
- PostgreSQL: Returns the document with data from the fallback locale
- MongoDB: Returns the document with empty arrays for localized fields

This difference caused `copyToLocale` to fail in MongoDB because the
merge algorithm only checked for `null` or `undefined` values, but not
empty arrays. When MongoDB returned `content: []` for a non-existent
locale, the algorithm would attempt to iterate over the empty array
instead of using the source locale's data.

### Move test e2e to int

The test introduced in #11887 didn't catch the bug because our e2e suite
doesn't run on Postgres. I migrated the test to an integration test that
does run on Postgres and MongoDB.
2025-07-24 20:37:13 +01:00
Jarrod Flesch
fa7d209cc9 fix(ui): incorrect blocks label sizing (#13264)
Blocks container labels should match the Array and Tab labels. Uses same
styling approach as Array labels.

### Before
<img width="229" height="260" alt="CleanShot 2025-07-24 at 12 26 38"
src="https://github.com/user-attachments/assets/9c4eb7c5-3638-4b47-805b-1206f195f5eb"
/>

### After
<img width="245" height="259" alt="CleanShot 2025-07-24 at 12 27 00"
src="https://github.com/user-attachments/assets/c04933b4-226f-403b-9913-24ba00857aab"
/>
2025-07-24 19:34:29 +00:00
Jacob Fletcher
bccf6ab16f feat: group by (#13138)
Supports grouping documents by specific fields within the list view.

For example, imagine having a "posts" collection with a "categories"
field. To report on each specific category, you'd traditionally filter
for each category, one at a time. This can be quite inefficient,
especially with large datasets.

Now, you can interact with all categories simultaneously, grouped by
distinct values.

Here is a simple demonstration:


https://github.com/user-attachments/assets/0dcd19d2-e983-47e6-9ea2-cfdd2424d8b5

Enable on any collection by setting the `admin.groupBy` property:

```ts
import type { CollectionConfig } from 'payload'

const MyCollection: CollectionConfig = {
  // ...
  admin: {
    groupBy: true
  }
}
```

This is currently marked as beta to gather feedback while we reach full
stability, and to leave room for API changes and other modifications.
Use at your own risk.

Note: when using `groupBy`, bulk editing is done group-by-group. In the
future we may support cross-group bulk editing.

Dependent on #13102 (merged).

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

---------

Co-authored-by: Paul Popus <paul@payloadcms.com>
2025-07-24 14:00:52 -04:00
Dan Ribbens
14322a71bb docs(plugin-import-export): document plugin-import-export (#13243)
Add documentation for @payloadcms/plugin-import-export.
2025-07-24 17:03:21 +00:00
Patrik
7e81d30808 fix(ui): ensure document unlocks when logging out from edit view of a locked document (#13142)
### What?

Refactors the `LeaveWithoutSaving` modal to be generic and delegates
document unlock logic back to the `DefaultEditView` component via a
callback.

### Why?

Previously, `unlockDocument` was triggered in a cleanup `useEffect` in
the edit view. When logging out from the edit view, the unlock request
would often fail due to the session ending — leaving the document in a
locked state.

### How?

- Introduced `onConfirm` and `onPrevent` props for `LeaveWithoutSaving`.
- Moved all document lock/unlock logic into `DefaultEditView`’s
`handleLeaveConfirm`.
- Captures the next navigation target via `onPrevent` and evaluates
whether to unlock based on:
  - Locking being enabled.
  - Current user owning the lock.
- Navigation not targeting internal admin views (`/preview`, `/api`,
`/versions`).

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-07-24 09:18:49 -07:00
Sasha
a83ed5ebb5 fix(db-postgres): search is broken when useAsTitle is not specified (#13232)
Fixes https://github.com/payloadcms/payload/issues/13171
2025-07-24 18:42:17 +03:00
Patrik
8f85da8931 fix(plugin-import-export): json preview and downloads preserve nesting and exclude disabled fields (#13210)
### What?

Improves both the JSON preview and export functionality in the
import-export plugin:
- Preserves proper nesting of object and array fields (e.g., groups,
tabs, arrays)
- Excludes any fields explicitly marked as `disabled` via
`custom.plugin-import-export`
- Ensures downloaded files use proper JSON formatting when `format` is
`json` (no CSV-style flattening)

### Why?

Previously:
- The JSON preview flattened all fields to a single level and included
disabled fields.
- Exported files with `format: json` were still CSV-style data encoded
as `.json`, rather than real JSON.

### How?

- Refactored `/preview-data` JSON handling to preserve original document
shape.
- Applied `removeDisabledFields` to clean nested fields using
dot-notation paths.
- Updated `createExport` to skip `flattenObject` for JSON formats, using
a nested JSON filter instead.
- Fixed streaming and buffered export paths to output valid JSON arrays
when `format` is `json`.
2025-07-24 11:36:46 -04:00
Jacob Fletcher
e48427e59a feat(ui): expose refresh method to list drawer context (#13173) 2025-07-24 10:12:45 -04:00
Sasha
7ae4f8c709 docs: add status to forbidden field names when using Postgres and drafts are enabled (#13233)
Fixes https://github.com/payloadcms/payload/issues/13144
2025-07-24 16:29:53 +03:00
Alessio Gravili
1ad7b55e05 refactor(drizzle): use getTableName utility (#13257)
~~Sometimes, drizzle is adding the same join to the joins array twice
(`addJoinTable`), despite the table being the same. This is due to a bug
in `getNameFromDrizzleTable` where it would sometimes return a UUID
instead of the table name.~~

~~This PR changes it to read from the drizzle:BaseName symbol instead,
which is correctly returning the table name in my testing. It falls back
to `getTableName`, which uses drizzle:Name.~~

This for some reason fails the tests. Instead, this PR just uses the
getTableName utility now instead of searching for the symbol manually.
2025-07-24 12:04:16 +00:00
Alessio Gravili
aeee0704dd chore: add new int test verifying that select *improves* performance of new optimization (#13254)
https://github.com/payloadcms/payload/pull/13186 actually made the
select API _more powerful_, as it can reduce the amount of db calls even
for complex collections with blocks down to 1.

This PR adds a test that verifies this.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210871676349303
2025-07-23 16:48:25 -07:00
Jarrod Flesch
29fb9ee5b4 fix(ui): monomorphic relationship fields should not show relationTo option labels (#13245) 2025-07-23 16:31:05 -04:00
Jacob Fletcher
0eac58ed72 fix(next): prevent base list filters from being injected into the url (#13253)
Prevents base list filters from being injected into the URL.

This is a problem with the multi-tenant plugin, for example, where
changing the tenant adds a `baseListFilter` to the query, but should
never be exposed to the end user.

Introduced in #13200.
2025-07-23 15:19:10 -04:00
Sasha
380ce04d5c perf(db-postgres): avoid including prettier to the bundle (#13251)
This PR optimizes bundle size with drizzle adapters by avoiding
including `prettier` to the production bundle
2025-07-23 19:05:31 +03:00
Alessio Gravili
94f5e790f6 perf(drizzle): single-roundtrip db updates for simple collections (#13186)
Currently, an optimized DB update (simple data => no
delete-and-create-row) does the following:
1. sql UPDATE
2. sql SELECT

This PR reduces this further to one single DB call for simple
collections:
1. sql UPDATE with RETURNING()

This only works for simple collections that do not have any fields that
need to be fetched from other tables. If a collection has fields like
relationship or blocks, we'll need that separate SELECT call to join in
the other tables.

In 4.0, we can remove all "complex" fields from the jobs collection and
replace them with a JSON field to make use of this optimization

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210803039809814
2025-07-23 01:45:55 -07:00
Elliot DeNolf
3f8fb6734c ci: default audit-dependencies script to high severity (#13244)
Default the audit-dependencies workflow to use high severity by default.
2025-07-22 16:44:56 -04:00
Jarrod Flesch
412bf4ff73 fix(ui): select all should reset when params change, page, filter, etc (#12612)
Fixes #11938
Fixes https://github.com/payloadcms/payload/issues/13154

When select-all is checked and you filter or change the page, the
selected documents should reset.
2025-07-22 15:23:02 -04:00
Patrik
246a42b727 chore(plugin-import-export): use debug-level logging for createExport process (#13242)
### What?

Replaces all `payload.logger.info` calls with `payload.logger.debug` in
the `createExport` function.

### Why?

info logs are too verbose. Using debug ensures detailed logs.

### How?

- Updated all logger calls in `createExport` to use `debug` instead of
`info`.
2025-07-22 18:09:04 +00:00
Elliot DeNolf
e7a652f0a8 build: suppress pnpm update notification (#13241)
Suppress pnpm update notification
2025-07-22 13:27:44 -04:00
Elliot DeNolf
77f279e768 docs: remove payload cloud (#13240)
Remove Payload Cloud from docs
2025-07-22 13:12:20 -04:00
Sasha
c1cfceb7dc fix(db-mongodb): handle duplicate unique index error for DocumentDB (#13239)
Currently, with DocumentDB instead of a friendly error like "Value must
be unique" we see a generic "Something went wrong" message.
This PR fixes that by adding a fallback to parse the message instead of
using `error.keyValue` which doesn't exist for responses from
DocumentDB.
2025-07-22 16:53:25 +00:00
fgrsource
0eb8f75946 docs: fix typo, example was not valid JSON (#13224)
### What?
A comma is missing in the example code. This results in not valid JSON.

### Why?
I stumbled upon it, while setting up a Tenant-based Payload for the
first time.

### How?
Adding a comma results in valid JSON.

Fixes #
Added a comma. ;)
2025-07-21 15:18:40 +00:00
Chandler Gonzales
af2ddff203 fix: text field validation for minLength: 1, required: false (#13124)
Fixes #13113

### How?

Does not rely on JS falseyness, instead explicitly checking for null &
undefined


I'm not actually certain this is the approach we want to take. Some
people might interpret "required" as not null, not-undefined and min
length > 1 in the case of strings. If they do, this change to the
behavior in the not-required case will break their expectations
2025-07-21 09:23:44 -04:00
Jessica Rynkar
dce898d7ca fix(ui): ensure publishSpecificLocale works during create operation (#13129)
### What?
This PR ensures that when a document is created using the `Publish in
__` button, it is saved to the correct locale.

### Why?
During document creation, the buttons `Publish` or `Publish in [locale]`
have the same effect. As a result, we overlooked the case where a user
may specifically click `Publish in [locale]` for the first save. In this
scenario, the create operation does not respect the
`publishSpecificLocale` value, so the document was always saved in the
default locale regardless of the intended one.

### How?
Passes the `publishSpecificLocale` value to the create operation,
ensuring the document and version is saved to the correct locale.

**Fixes:** #13117
2025-07-21 09:19:51 -04:00
Jarrod Flesch
7f9de6d101 fix: empty folderType arrays break relational dbs (#13219)
Relational databases were broken with folders because it was querying
on:
```ts
{
  folderType: {
    equals: []
  }
}
```

Which does not work since the select hasMany stores values in a separate
table.
2025-07-21 08:39:18 -04:00
German Jablonski
d6e21adaf0 docs: shorten line length in code snippet comments to avoid horizontal scrolling (#13217)
prettier doesn't seem to cover that, and horizontal scrolling in the
browser is even more annoying than in the IDE.
Regex used in the search engine: `^[ \t]*\* `
2025-07-18 15:28:44 +01:00
Jacob Fletcher
d7a3faa4e9 fix(ui): properly sync search params to user preferences (#13200)
Some search params within the list view do not properly sync to user
preferences, and visa versa.

For example, when selecting a query preset, the `?preset=123` param is
injected into the URL and saved to preferences, but when reloading the
page without the param, that preset is not reactivated as expected.

### Problem 

The reason this wasn't working before is that omitting this param would
also reset prefs. It was designed this way in order to support
client-side resets, e.g. clicking the query presets "x" button.

This pattern would never work, however, because this means that every
time the user navigates to the list view directly, their preference is
cleared, as no param would exist in the query.

Note: this is not an issue with _all_ params, as not all are handled in
the same way.

### Solution

The fix is to use empty values instead, e.g. `?preset=`. When the server
receives this, it knows to clear the pref. If it doesn't exist at all,
it knows to load from prefs. And if it has a value, it saves to prefs.
On the client, we sanitize those empty values back out so they don't
appear in the URL in the end.

This PR also refactors much of the list query context and its respective
provider to be significantly more predictable and easier to work with,
namely:

- The `ListQuery` type now fully aligns with what Payload APIs expect,
e.g. `page` is a number, not a string
- The provider now receives a single `query` prop which matches the
underlying context 1:1
- Propagating the query from the server to the URL is significantly more
predictable
- Any new props that may be supported in the future will automatically
work
- No more reconciling `columns` and `listPreferences.columns`, its just
`query.columns`

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210827129744922
2025-07-18 09:29:26 -04:00
iamacup
46d8a26b0d fix: handle undefined values in afterChange hooks when read:false and create:true on the field level access for parents and siblings (#12664)
<!--

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 bug where `afterChange` hooks would attempt to access values for
fields that are `read: false` but `create: true`, resulting in
`undefined` values and unexpected behavior.

### Why?

In scenarios where access control allows field creation (`create: true`)
but disallows reading it (`read: false`), hooks like `afterChange` would
still attempt to operate on `undefined` values from `siblingDoc` or
`previousDoc`, potentially causing errors or skipped logic.

### How?

Adds safe optional chaining and fallback object initialization in
`promise.ts` for:
- `previousDoc[field.name]`
- `siblingDoc[field.name]`
- Group, Array, and Block field traversals

This ensures that these values are treated as empty objects or arrays
where appropriate to prevent runtime errors during traversal or hook
execution.

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

---------

Co-authored-by: Niall Bambury <niall.bambury@cuckoo.co>
2025-07-18 13:34:54 +01:00
Alessio Gravili
c08b2aea89 feat: scheduling jobs (#12863)
Adds a new `schedule` property to workflow and task configs that can be
used to have Payload automatically _queue_ jobs following a certain
_schedule_.

Docs:
https://payloadcms.com/docs/dynamic/jobs-queue/schedules?branch=feat/schedule-jobs

## API Example

```ts
export default buildConfig({
  // ...
  jobs: {
    // ...
    scheduler: 'manual', // Or `cron` if you're not using serverless. If `manual` is used, then user needs to set up running /api/payload-jobs/handleSchedules or payload.jobs.handleSchedules in regular intervals
    tasks: [
      {
        schedule: [
          {
            cron: '* * * * * *',
            queue: 'autorunSecond',
            // Hooks are optional
            hooks: {
              // Not an array, as providing and calling `defaultBeforeSchedule` would be more error-prone if this was an array
              beforeSchedule: async (args) => {
                // Handles verifying that there are no jobs already scheduled or processing.
                // You can override this behavior by not calling defaultBeforeSchedule, e.g. if you wanted
                // to allow a maximum of 3 scheduled jobs in the queue instead of 1, or add any additional conditions
                const result = await args.defaultBeforeSchedule(args)
                return {
                  ...result,
                  input: {
                    message: 'This task runs every second',
                  },
                }
              },
              afterSchedule: async (args) => {
                await args.defaultAfterSchedule(args) // Handles updating the payload-jobs-stats global
                args.req.payload.logger.info(
                  'EverySecond task scheduled: ' +
                  (args.status === 'success' ? args.job.id : 'skipped or failed to schedule'),
                )
              },
            },
          },
        ],
        slug: 'EverySecond',
        inputSchema: [
          {
            name: 'message',
            type: 'text',
            required: true,
          },
        ],
        handler: ({ input, req }) => {
          req.payload.logger.info(input.message)
          return {
            output: {},
          }
        },
      }
    ]
  }
})
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210495300843759
2025-07-18 06:48:27 -04:00
Jake Fell
4ae503d700 fix: exit payload jobs:run process after completion (#13211)
### What?

Exit the process after running jobs.

### Why?

When running the `payload jobs:run` bin script with a postgres database
the process hangs forever.

### How?

Execute `process.exit(0)` after running all jobs.
2025-07-17 19:33:49 +00:00
Elliot DeNolf
a3361356b2 chore(release): v3.48.0 [skip ci] 2025-07-17 14:45:59 -04:00
Patrik
95e373e60b fix(plugin-import-export): disabled flag to cascade to nested fields from parent containers (#13199)
### What?

Fixes the `custom.plugin-import-export.disabled` flag to correctly
disable fields in all nested structures including:
- Groups
- Arrays
- Tabs
- Blocks

Previously, only top-level fields or direct children were respected.
This update ensures nested paths (e.g. `group.array.field1`,
`blocks.hero.title`, etc.) are matched and filtered from exports.

### Why?

- Updated regex logic in both `createExport` and Preview components to
recursively support:
  - Indexed array fields (e.g. `array_0_field1`)
  - Block fields with slugs (e.g. `blocks_0_hero_title`)
  - Nested field accessors with correct part-by-part expansion

### How?

To allow users to disable entire field groups or deeply nested fields in
structured layouts.
2025-07-17 18:12:58 +00:00
Jarrod Flesch
12539c61d4 feat(ui): supports collection scoped folders (#12797)
As discussed in [this
RFC](https://github.com/payloadcms/payload/discussions/12729), this PR
supports collection-scoped folders. You can scope folders to multiple
collection types or just one.

This unlocks the possibility to have folders on a per collection instead
of always being shared on every collection. You can combine this feature
with the `browseByFolder: false` to completely isolate a collection from
other collections.

Things left to do:
- [x] ~~Create a custom react component for the selecting of
collectionSlugs to filter out available options based on the current
folders parameters~~


https://github.com/user-attachments/assets/14cb1f09-8d70-4cb9-b1e2-09da89302995


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210564397815557
2025-07-17 13:24:22 -04:00
Alessio Gravili
6ae730b33b feat(richtext-lexical): export $createLinkNode and $isLinkNode for server use (#13205)
Exports `$createLinkNode`, `$isLinkNode` and the equivalent modules for
autolinks.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210710489889573
2025-07-17 09:24:37 -04:00
Sasha
a20b43624b feat: add findDistinct operation (#13102)
Adds a new operation findDistinct that can give you distinct values of a
field for a given collection
Example:
Assume you have a collection posts with multiple documents, and some of
them share the same title:
```js
// Example dataset (some titles appear multiple times)
[
  { title: 'title-1' },
  { title: 'title-2' },
  { title: 'title-1' },
  { title: 'title-3' },
  { title: 'title-2' },
  { title: 'title-4' },
  { title: 'title-5' },
  { title: 'title-6' },
  { title: 'title-7' },
  { title: 'title-8' },
  { title: 'title-9' },
]
```
You can now retrieve all unique title values using findDistinct:
```js
const result = await payload.findDistinct({
  collection: 'posts',
  field: 'title',
})

console.log(result.values)
// Output:
// [
//   'title-1',
//   'title-2',
//   'title-3',
//   'title-4',
//   'title-5',
//   'title-6',
//   'title-7',
//   'title-8',
//   'title-9'
// ]
```
You can also limit the number of distinct results:
```js
const limitedResult = await payload.findDistinct({
  collection: 'posts',
  field: 'title',
  sortOrder: 'desc',
  limit: 3,
})

console.log(limitedResult.values)
// Output:
// [
//   'title-1',
//   'title-2',
//   'title-3'
// ]
```

You can also pass a `where` query to filter the documents.
2025-07-16 17:18:14 -04:00
Sean Zubrickas
cab7ba4a8a fix: Enhances field-level access controls on Users collection to address s… (#13197)
Enhance field-level access controls on Users collection to address
security concerns

- Restricted read/update access on `email` field to admins and the user
themselves
- Locked down `roles` field so only admins can create, read, or update
it
2025-07-16 15:36:32 -04:00
Elliott W
41cff6d436 fix(db-mongodb): improve compatibility with Firestore database (#12763)
### What?

Adds four more arguments to the `mongooseAdapter`:

```typescript
  useJoinAggregations?: boolean  /* The big one */
  useAlternativeDropDatabase?: boolean
  useBigIntForNumberIDs?: boolean
  usePipelineInSortLookup?: boolean
```

Also export a new `compatabilityOptions` object from
`@payloadcms/db-mongodb` where each key is a mongo-compatible database
and the value is the recommended `mongooseAdapter` settings for
compatability.

### Why?

When using firestore and visiting
`/admin/collections/media/payload-folders`, we get:

```
MongoServerError: invalid field(s) in lookup: [let, pipeline], only lookup(from, localField, foreignField, as) is supported
```

Firestore doesn't support the full MongoDB aggregation API used by
Payload which gets used when building aggregations for populating join
fields.

There are several other compatability issues with Firestore:
- The invalid `pipeline` property is used in the `$lookup` aggregation
in `buildSortParams`
- Firestore only supports number IDs of type `Long`, but Mongoose
converts custom ID fields of type number to `Double`
- Firestore does not support the `dropDatabase` command
- Firestore does not support the `createIndex` command (not addressed in
this PR)

### How?

 ```typescript
useJoinAggregations?: boolean  /* The big one */
```
When this is `false` we skip the `buildJoinAggregation()` pipeline and resolve the join fields through multiple queries. This can potentially be used with AWS DocumentDB and Azure Cosmos DB to support join fields, but I have not tested with either of these databases.

 ```typescript
useAlternativeDropDatabase?: boolean
```
When `true`, monkey-patch (replace) the `dropDatabase` function so that
it calls `collection.deleteMany({})` on every collection instead of
sending a single `dropDatabase` command to the database

 ```typescript
useBigIntForNumberIDs?: boolean
```
When `true`, use `mongoose.Schema.Types.BigInt` for custom ID fields of type `number` which converts to a firestore `Long` behind the scenes

```typescript
  usePipelineInSortLookup?: boolean
```
When `false`, modify the sortAggregation pipeline in `buildSortParams()` so that we don't use the `pipeline` property in the `$lookup` aggregation. Results in slightly worse performance when sorting by relationship properties.

### Limitations

This PR does not add support for transactions or creating indexes in firestore.

### Fixes

Fixed a bug (and added a test) where you weren't able to sort by multiple properties on a relationship field.

### Future work

1. Firestore supports simple `$lookup` aggregations but other databases might not. Could add a `useSortAggregations` property which can be used to disable aggregations in sorting.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-07-16 15:17:43 -04:00
Elliot DeNolf
e6da384a43 ci: disable bundle analysis for forks (#13198)
The bundle analysis action requires comment permissions which are not
available to PRs from forks.

This PR disables bundle analysis until we can implement this in a
separate workflow as shown in [the docs
here](https://github.com/exoego/esbuild-bundle-analyzer?tab=readme-ov-file#github-action-setup-for-public-repositories).
2025-07-16 12:56:42 -04:00
Alessio Gravili
7cd682c66a perf(drizzle): further optimize postgres row updates (#13184)
This is a follow-up to https://github.com/payloadcms/payload/pull/13060.

There are a bunch of other db adapter methods that use `upsertRow` for
updates: `updateGlobal`, `updateGlobalVersion`, `updateJobs`,
`updateMany`, `updateVersion`.

The previous PR had the logic for using the optimized row updating logic
inside the `updateOne` adapter. This PR moves that logic to the original
`upsertRow` function. Benefits:
- all the other db methods will benefit from this massive optimization
as well. This will be especially relevant for optimizing postgres job
queue initial updates - we should be able to close
https://github.com/payloadcms/payload/pull/11865 after another follow-up
PR
- easier to read db adapter methods due to less code.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210803039809810
2025-07-16 09:45:02 -07:00
jangir-ritik
be8e8d9c7f docs: fix minor typo (#13185) 2025-07-16 05:46:17 +00:00
Sasha
841bf891d0 feat: atomic number field updates (#13118)
Based on https://github.com/payloadcms/payload/pull/13060 which should
be merged first
This PR adds ability to update number fields atomically, which could be
important with parallel writes. For now we support this only via
`payload.db.updateOne`.

For example:
```js
// increment by 10
const res = await payload.db.updateOne({
  data: {
    number: {
      $inc: 10,
    },
  },
  collection: 'posts',
  where: { id: { equals: post.id } },
})

// decrement by 3
const res2 = await payload.db.updateOne({
  data: {
    number: {
      $inc: -3,
    },
  },
  collection: 'posts',
  where: { id: { equals: post.id } },
})
```
2025-07-15 21:53:45 -07:00
Patrik
2a59c5bf8c fix(plugin-import-export): export field dropdown to properly label and path fields in named/unnamed tabs (#13180)
### What?

Fixes the export field selection dropdown to correctly differentiate
between fields in named and unnamed tabs.

### Why?

Previously, when a `tabs` field contained both named and unnamed tabs,
subfields with the same `name` would appear as duplicates in the
dropdown (e.g. `Tab To CSV`, `Tab To CSV`). Additionally, selecting a
field from a named tab would incorrectly map it to the unnamed version
due to shared labels and missing path prefixes.

### How?

- Updated the `reduceFields` utility to manually construct the field
path and label using the tab’s `name` if present.
- Ensured unnamed tabs treat subfields as top-level and skip prefixing
altogether.
- Adjusted label prefix logic to show `Named Tab > Field Name` when
appropriate.


#### Before
<img width="169" height="79" alt="Screenshot 2025-07-15 at 2 55 14 PM"
src="https://github.com/user-attachments/assets/2ab2d19e-41a3-4be2-8496-1da2a79f88e1"
/>

#### After
<img width="211" height="79" alt="Screenshot 2025-07-15 at 2 50 38 PM"
src="https://github.com/user-attachments/assets/0620e96a-71cd-4eb1-9396-30d461ed47a5"
/>
2025-07-15 16:41:07 -04:00
Alessio Gravili
64d76a3869 fix: cron jobs running when calling bin scripts, leading to db errors (#13135)
Previously, we were always initializing cronjobs when calling
`getPayload` or `payload.init`.

This is undesired in bin scripts - we don't want cron jobs to start
triggering db calls while we're running an initial migration using
`payload migrate` for example. This has previously led to a race
condition, triggering the following, occasional error, if job autoruns
were enabled:

```ts
DrizzleQueryError: Failed query: select "payload_jobs"."id", "payload_jobs"."input", "payload_jobs"."completed_at", "payload_jobs"."total_tried", "payload_jobs"."has_error", "payload_jobs"."error", "payload_jobs"."workflow_slug", "payload_jobs"."task_slug", "payload_jobs"."queue", "payload_jobs"."wait_until", "payload_jobs"."processing", "payload_jobs"."updated_at", "payload_jobs"."created_at", "payload_jobs_log"."data" as "log" from "payload_jobs" "payload_jobs" left join lateral (select coalesce(json_agg(json_build_array("payload_jobs_log"."_order", "payload_jobs_log"."id", "payload_jobs_log"."executed_at", "payload_jobs_log"."completed_at", "payload_jobs_log"."task_slug", "payload_jobs_log"."task_i_d", "payload_jobs_log"."input", "payload_jobs_log"."output", "payload_jobs_log"."state", "payload_jobs_log"."error") order by "payload_jobs_log"."_order" asc), '[]'::json) as "data" from (select * from "payload_jobs_log" "payload_jobs_log" where "payload_jobs_log"."_parent_id" = "payload_jobs"."id" order by "payload_jobs_log"."_order" asc) "payload_jobs_log") "payload_jobs_log" on true where ("payload_jobs"."completed_at" is null and ("payload_jobs"."has_error" is null or "payload_jobs"."has_error" <> $1) and "payload_jobs"."processing" = $2 and ("payload_jobs"."wait_until" is null or "payload_jobs"."wait_until" < $3) and "payload_jobs"."queue" = $4) order by "payload_jobs"."created_at" asc limit $5
params: true,false,2025-07-10T21:25:03.002Z,autorunSecond,100
    at NodePgPreparedQuery.queryWithCache (/Users/alessio/Documents/GitHub/payload2/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/pg-core/session.ts:74:11)
    at processTicksAndRejections (node:internal/process/task_queues:105:5)
    at /Users/alessio/Documents/GitHub/payload2/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/node-postgres/session.ts:154:19
    ... 6 lines matching cause stack trace ...
    at N._trigger (/Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/croner@9.0.0/node_modules/croner/dist/croner.cjs:1:16806) {
  query: `select "payload_jobs"."id", "payload_jobs"."input", "payload_jobs"."completed_at", "payload_jobs"."total_tried", "payload_jobs"."has_error", "payload_jobs"."error", "payload_jobs"."workflow_slug", "payload_jobs"."task_slug", "payload_jobs"."queue", "payload_jobs"."wait_until", "payload_jobs"."processing", "payload_jobs"."updated_at", "payload_jobs"."created_at", "payload_jobs_log"."data" as "log" from "payload_jobs" "payload_jobs" left join lateral (select coalesce(json_agg(json_build_array("payload_jobs_log"."_order", "payload_jobs_log"."id", "payload_jobs_log"."executed_at", "payload_jobs_log"."completed_at", "payload_jobs_log"."task_slug", "payload_jobs_log"."task_i_d", "payload_jobs_log"."input", "payload_jobs_log"."output", "payload_jobs_log"."state", "payload_jobs_log"."error") order by "payload_jobs_log"."_order" asc), '[]'::json) as "data" from (select * from "payload_jobs_log" "payload_jobs_log" where "payload_jobs_log"."_parent_id" = "payload_jobs"."id" order by "payload_jobs_log"."_order" asc) "payload_jobs_log") "payload_jobs_log" on true where ("payload_jobs"."completed_at" is null and ("payload_jobs"."has_error" is null or "payload_jobs"."has_error" <> $1) and "payload_jobs"."processing" = $2 and ("payload_jobs"."wait_until" is null or "payload_jobs"."wait_until" < $3) and "payload_jobs"."queue" = $4) order by "payload_jobs"."created_at" asc limit $5`,
  params: [ true, false, '2025-07-10T21:25:03.002Z', 'autorunSecond', 100 ],
  cause: error: relation "payload_jobs" does not exist
      at /Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/pg@8.16.3/node_modules/pg/lib/client.js:545:17
      at processTicksAndRejections (node:internal/process/task_queues:105:5)
      at /Users/alessio/Documents/GitHub/payload2/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/node-postgres/session.ts:161:13
      at NodePgPreparedQuery.queryWithCache (/Users/alessio/Documents/GitHub/payload2/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/pg-core/session.ts:72:12)
      at /Users/alessio/Documents/GitHub/payload2/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/node-postgres/session.ts:154:19
      at find (/Users/alessio/Documents/GitHub/payload2/packages/drizzle/src/find/findMany.ts:162:19)
      at Object.updateMany (/Users/alessio/Documents/GitHub/payload2/packages/drizzle/src/updateJobs.ts:26:16)
      at updateJobs (/Users/alessio/Documents/GitHub/payload2/packages/payload/src/queues/utilities/updateJob.ts:102:37)
      at runJobs (/Users/alessio/Documents/GitHub/payload2/packages/payload/src/queues/operations/runJobs/index.ts:181:25)
      at Object.run (/Users/alessio/Documents/GitHub/payload2/packages/payload/src/queues/localAPI.ts:137:12)
      at N.fn (/Users/alessio/Documents/GitHub/payload2/packages/payload/src/index.ts:866:13)
      at N._trigger (/Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/croner@9.0.0/node_modules/croner/dist/croner.cjs:1:16806) {
    length: 112,
    severity: 'ERROR',
    code: '42P01',
    detail: undefined,
    hint: undefined,
    position: '406',
    internalPosition: undefined,
    internalQuery: undefined,
    where: undefined,
    schema: undefined,
    table: undefined,
    column: undefined,
    dataType: undefined,
    constraint: undefined,
    file: 'parse_relation.c',
    line: '1449',
    routine: 'parserOpenTable'
  }
}
```

This PR makes running crons opt-in using a new `cron` flag. By default,
no cron jobs will be created.
2025-07-15 13:24:50 -04:00
Jarrod Flesch
5f019533d8 fix: types for RenderField fields prop (#13162)
Fixes #7799 

Fixes a type issue where all fields in RenderFields['fields'] admin
properties were being marked as required since we were using `Pick`.
Adds a helper type to allow extracting properties with correct
optionality.
2025-07-15 09:12:33 -04:00
Jacob Fletcher
277448d9c0 docs: performance (#13068)
Payload is designed with performance in mind, but its customizability
means that there are many ways to configure your app that can impact
performance.

While Payload provides several features and best practices to help you
optimize your app's specific performance needs, these are not currently
well surfaced and can be obscure.

Now:

- A high-level performance doc now exists at `/docs/performance`
- There's a new section on performance within the `/docs/queries` doc
- There's a new section on performance within the `/docs/hooks` doc
- There's a new section on performance within the
`/docs/custom-components` doc

This PR also:

- Restructures and elaborates on the `/docs/queries/pagination` docs
- Adds a new `/docs/database/indexing` doc
- More

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210743577153856
2025-07-14 23:55:16 -04:00
Patrik
5839cb61fa feat(plugin-import-export): adds support for disabling fields (#13166)
### What?

Adds support for excluding specific fields from the import-export plugin
using a custom field config.

### Why?

Some fields should not be included in exports or previews. This feature
allows users to flag those fields directly in the field config.

### How?

- Introduced a `plugin-import-export.disabled: true` custom field
property.
- Automatically collects and stores disabled field accessors in
`collection.admin.custom['plugin-import-export'].disabledFields`.
- Excludes these fields from the export field selector, preview table,
and final export output (CSV/JSON).
2025-07-14 17:10:36 -04:00
Elliot DeNolf
f4d951dd04 templates: bump for v3.47.0 (#13161)
🤖 Automated bump of templates for v3.47.0

Triggered by user: @AlessioGr

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-14 13:42:11 -07:00
Patrik
7294cf561d feat(plugin-import-export): adds support for forcing export format via plugin config (#13160)
### What?

Adds a new `format` option to the `plugin-import-export` config that
allows users to force the export format (`csv` or `json`) and hide the
format dropdown from the export UI.

### Why?

In some use cases, allowing the user to select between CSV and JSON is
unnecessary or undesirable. This new option allows plugin consumers to
lock the format and simplify the export interface.

### How?

- Added a `format?: 'csv' | 'json'` field to `ImportExportPluginConfig`.
- When defined, the `format` field in the export UI is:
  - Hidden via `admin.condition`
  - Pre-filled via `defaultValue`
- Updated `getFields` to accept the plugin config and apply logic
accordingly.

### Example

```ts
importExportPlugin({
  format: 'json',
})
2025-07-14 16:19:52 -04:00
Alessio Gravili
4831bae6b5 docs: fix invalid syntax failing the docs import (#13165)
ts is recognized, typescript is not
2025-07-14 19:39:56 +00:00
Alessio Gravili
4c69f8e205 docs: add section on browser environment variables when using experimental-build-mode (#13164)
Just spent an entire hour trying to figure out why my environment
variables are `undefined` on the client. Turns out, when running `pnpm
next build --experimental-build-mode compile`, it skips the environment
variable inlining step.

This adds a new section to the docs mentioning that you can use `pnpm
next build --experimental-build-mode generate-env` to manually inline
them.
2025-07-14 12:30:23 -07:00
Patrik
de53f689e3 feat(plugin-import-export): adds pluginConfig options to disable Save and Download buttons in export UI (#13158)
### What?

Adds support for two new plugin options in the import-export plugin:
- `disableSave`: disables the "Save" button in the export UI view.
- `disableDownload`: disables the "Download" button in the export UI
view.

### Why?

This allows implementers to control user access to export actions based
on context or feature requirements. For example, some use cases may want
to preview an export without saving it, or disable downloads entirely.

### How?

- Injected `disableSave` and `disableDownload` into `admin.custom` for
the `exports` collection.
- Updated the `ExportSaveButton` component to conditionally render each
button based on the injected flags.
- Defaults to `false` if the values are not explicitly set in
`pluginConfig`.

### Example

```ts
importExportPlugin({
  disableSave: true, // Defaults to false
  disableDownload: true, // Defaults to false
})
2025-07-14 11:16:14 -07:00
Alessio Gravili
edd1f02eb5 ci: fix post-release-templates workflow (#13159)
Fixes the post-release-templates workflow by building payload before
running the gen templates script

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210784057163370
2025-07-14 14:03:50 -04:00
Patrick Roelofs
d213c9150d fix(plugin-seo): add localized property to MetaTitleComponent (#12751)
### What?
I noticed the plugin-seo exported field component MetaTitleComponent was
missing a localized field property in the UI. Localization is working,
it just wasnt represented in the UI

### Why?
This improves the localization UI for plugin-seo

### How?
The localized prop wasn't being passed along to the Label component

This implementation is a direct copy of the implementation in the
MetaDescription

https://github.com/payloadcms/payload/blob/main/packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx

Screenshot of issue:

![image](https://github.com/user-attachments/assets/fd3fcd2e-169a-4ca0-915d-0ed8d85e6abf)

Co-authored-by: Patrick Roelofs <patrick.roelofs@iquality.nl>
2025-07-13 12:39:30 +00:00
Jarrod Flesch
a57faebfd7 test: move i18n list tests to i18n suite (#13143) 2025-07-12 07:03:26 -04:00
Jarrod Flesch
53ad02a8e8 test: fix flaky uploads test (#13141) 2025-07-12 07:03:12 -04:00
Jarrod Flesch
e5755110a9 fix(plugin-multi-tenant): selector could become hidden (#13134)
The selector could become hidden by:
- logging in with a user that only has 1 tenant
- logging out
- logging in with a user that has more than 1 tenant

Simplifies useEffect usage. Adds e2e test for this case.
2025-07-11 16:34:55 -04:00
Elliot DeNolf
e5f64f7952 chore(release): v3.47.0 [skip ci] 2025-07-11 15:43:44 -04:00
Jarrod Flesch
2cafe494cc fix(ui): disabled and styles add row button correctly (#13140)
Disables add row button using disabled prop from useField - i.e. when
the form is processing or initializing.

This fixes a flaky array test that clicks the button before the form has
finished initializing/processing.

Also corrects the add row button color styles with specificity.
2025-07-11 14:07:51 -04:00
German Jablonski
576644d0b5 docs(richtext-lexical): add documentation page about official features (#13132)
It was evident from the number of users asking questions about how to
use the features that a dedicated page was needed.

Preview:

https://payloadcms.com/docs/dynamic/rich-text/official-features?branch=features-docs
2025-07-11 10:02:06 -07:00
Aaron Claes
8a3b97c643 feat(ui): add API key visibility toggle (#13110) 2025-07-11 12:50:38 -04:00
Patrik
06ef798653 fix(ui): ensure buildFormStateHandler throws error instead of returning null for unauthorized requests (#13123)
### What?

Prevents `buildFormStateHandler` from returning `null` in unauthorized
scenarios by throwing an explicit `Error` instead.

### Why?

The `BuildFormStateResult` type does not include `null`, but previously
the handler returned `null` when access was unauthorized. This caused
runtime type mismatches and forced client-side workarounds (e.g.
guarding destructures).

By always throwing instead of returning `null`, the client code can
safely assume a valid result or catch errors.

<img width="1772" height="723" alt="Screenshot_2025-07-10_185618"
src="https://github.com/user-attachments/assets/d65344e3-a2cb-4ec5-91bf-a353b5b7dd14"
/>

### How?

- Replaced the `return null` with `throw new Error('Unauthorized')` in
`buildFormStateHandler`.
- Client code no longer needs to handle `null` responses from
`getFormState`.
2025-07-11 12:19:11 -04:00
Jessica Rynkar
5695d22a46 fix: execute mimetype validation on the file buffer data (#13117)
### What
Introduces an additional `mimeType` validation based on the actual file
data to ensure the uploaded file matches the allowed `mimeTypes` defined
in the upload config.

### Why?
The current validation relies on the file extension, which can be easily
manipulated. For example, if only PDFs are allowed, a JPEG renamed to
`image.pdf` would bypass the check and be accepted. This change prevents
such cases by verifying the true MIME type.

### How?
Performs a secondary validation using the file’s binary data (buffer),
providing a more reliable MIME type check.

Fixes #12905
2025-07-11 16:56:55 +01:00
Jarrod Flesch
19a3367972 fix(ui): monomorphic joins tables not fetching draft documents (#13139)
Monomorphic join fields were not using the `draft` argument when
fetching documents to display in the table. This change makes the join
field treatment of drafts consistent with the `relationship` type
fields.

Added e2e test to cover.
2025-07-11 14:26:48 +00:00
Patrik
c1bad0115a fix(plugin-import-export): sync export field selection with list view columns from query columns (#13131)
### What?

Updated the `FieldsToExport` component to use the current list view
query (`query.columns`) instead of saved preferences to determine which
fields to export.

### Why?

Previously, the export field selection was based on collection
preferences, which are only updated on page reload. This caused stale or
incorrect field sets to be exported if the user changed visible columns
without refreshing.

### How?

- Replaced `getPreference` usage with `useListQuery` to access
`query.columns`
- Filtered out excluded fields (those prefixed with `-`) to get only the
visible columns
- Fallbacks to `defaultColumns` if `query.columns` is not available
2025-07-10 19:02:05 +00:00
Patrik
b3a994ed6f feat(plugin-import-export): show delayed toast when export download takes time (#13126)
### What?

Added a delayed toast message to indicate when an export is being
processed, and disabled the download button unless the export form has
been modified.

### Why?

Previously, there was no feedback during longer export operations, which
could confuse users if the request took time to complete. Also, the
download button was always enabled, even when the form had not been
modified — which could lead to unnecessary exports.

### How?

- Introduced a 200ms delay before showing a "Your export is being
processed..." toast
- Automatically dismisses the toast once the download completes or fails
- Hooked into `useFormModified` to:
  - Track whether the export form has been changed
  - Disable the download button when the form is unmodified
  - Reset the modified state after triggering a download
2025-07-10 14:55:46 -04:00
Paul
f63dfad565 fix(ui): ensure that schedule publishing time picker can only be in the future (#13128)
Previously you could've selected a date and time in the past to schedule
publish.

Now we ensure that there is a minimum time and date for scheduled
publishing date picker.


Additionally updated the disabled items to be more visually obvious that
they are disabled:
<img width="404" height="336" alt="image"
src="https://github.com/user-attachments/assets/1f4ea36a-267e-4ae5-91e4-92bb84d7889c"
/>
2025-07-10 11:01:55 -07:00
Paul
2d91cb613c feat: allow joins, select, populate, depth and draft to /me REST API operation (#13116)
While we can use `joins`, `select`, `populate`, `depth` or `draft` on
auth collections when finding or finding by ID, these arguments weren't
supported for `/me` which meant that in some situations like in our
ecommerce template we couldn't optimise these calls.

A workaround would be to make a call to `/me` and then get the user ID
to then use for a `findByID` operation.
2025-07-10 17:44:05 +00:00
Jarrod Flesch
0c2b1054e2 fix: login operation not returning collection and _strategy (#13119)
The login operation with sessions enabled calls updateOne, in mongodb,
data that does not match the schema is removed. `collection` and
`_strategy` are not part of the schema so they need to be reassigned
after the user is updated.

Adds int test.
2025-07-10 12:13:01 -04:00
Paul
cb6a73e1b4 feat(storage-*): include modified headers into the response headers of files when using adapters (#12096)
This PR makes it so that `modifyResponseHeaders` is supported in our
adapters when set on the collection config. Previously it would be
ignored.

This means that users can now modify or append new headers to what's
returned by each service.

```ts
import type { CollectionConfig } from 'payload'

export const Media: CollectionConfig = {
  slug: 'media',
  upload: {
    modifyResponseHeaders: ({ headers }) => {
      const newHeaders = new Headers(headers) // Copy existing headers
      newHeaders.set('X-Frame-Options', 'DENY') // Set new header

      return newHeaders
    },
  },
}
```

Also adds support for `void` return on the `modifyResponseHeaders`
function in the case where the user just wants to use existing headers
and doesn't need more control.

eg:

```ts
import type { CollectionConfig } from 'payload'

export const Media: CollectionConfig = {
  slug: 'media',
  upload: {
    modifyResponseHeaders: ({ headers }) => {
      headers.set('X-Frame-Options', 'DENY') // You can directly set headers without returning
    },
  },
}
```

Manual testing checklist (no CI e2es setup for these envs yet):
- [x] GCS
- [x] S3
- [x] Azure
- [x] UploadThing
- [x] Vercel Blob

---------

Co-authored-by: James <james@trbl.design>
2025-07-10 08:00:26 -07:00
Sasha
055cc4ef12 perf(db-postgres): simplify db.updateOne to a single DB call with if the passed data doesn't include nested fields (#13060)
In case, if `payload.db.updateOne` received simple data, meaning no:
* Arrays / Blocks
* Localized Fields
* `hasMany: true` text / select / number / relationship fields
* relationship fields with `relationTo` as an array

This PR simplifies the logic to a single SQL `set` call. No any extra
(useless) steps with rewriting all the arrays / blocks / localized
tables even if there were no any changes to them. However, it's good to
note that `payload.update` (not `payload.db.updateOne`) as for now
passes all the previous data as well, so this change won't have any
effect unless you're using `payload.db.updateOne` directly (or for our
internal logic that uses it), in the future a separate PR with
optimization for `payload.update` as well may be implemented.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210710489889576
2025-07-10 16:49:12 +03:00
Jarrod Flesch
c77b39c3b4 fix(ui): hidden input should wait for form initialization (#13114) 2025-07-10 06:15:09 -04:00
Germán Jabloñski
5e82f9ff41 feat(next): redirect non-existent documents to list view with banner (#13062)
Currently, when a nonexistent document is accessed via the URL, a
`NotFound` page is displayed with a button to return to the dashboard.

In most cases, the next step the user will take is to navigate to the
list of documents in that collection. If we automatically redirect users
to the list view and display the error in a banner, we can save them a
couple of redirects.

This is a very common scenario when writing tests or restarting the
local environment.


## Before


![image](https://github.com/user-attachments/assets/ea7af410-5567-4dd2-b44b-67177aa795e6)


## After

![image](https://github.com/user-attachments/assets/72b38d2f-63f2-4a2b-94c4-76ea90d80c24)
2025-07-10 03:10:37 -07:00
Patrik
c6105f1e0d fix(plugin-import-export): flattening logic for polymorphic relationships in CSV exports (#13094)
### What?

Improves the flattening logic used in the import-export plugin to
correctly handle polymorphic relationships (both `hasOne` and `hasMany`)
when generating CSV columns.

### Why?

Previously, `hasMany` polymorphic relationships would flatten their full
`value` object recursively, resulting in unwanted keys like `createdAt`,
`title`, `email`, etc. This change ensures that only the `id` and
`relationTo` fields are included, matching how `hasOne` polymorphic
fields already behave.

### How?

- Updated `flattenObject` to special-case `hasMany` polymorphic
relationships and extract only `relationTo` and `id` per index.
- Refined `getFlattenedFieldKeys` to return correct column keys for
polymorphic fields:
  - `hasMany polymorphic → name_0_relationTo`, `name_0_id`
  - `hasOne polymorphic → name_relationTo`, `name_id`
  - `monomorphic → name` or `name_0`
- **Added try/catch blocks** around `toCSVFunctions` calls in
`flattenObject`, with descriptive error messages including the column
path and input value. This improves debuggability if a custom `toCSV`
function throws.
2025-07-09 15:46:48 -04:00
Patrik
0806ee1762 fix(plugin-import-export): selectionToUse field to dynamically show valid export options (#13092)
### What?

Updated the `selectionToUse` export field to properly render a radio
group with dynamic options based on current selection state and applied
filters.

- Fixed an edge case where `currentFilters` would appear as an option
even when the `where` clause was empty (e.g. `{ or: [] }`).

### Why?

Previously, the `selectionToUse` field displayed all options (current
selection, current filters, all documents) regardless of context. This
caused confusion when only one of them was applicable.

### How?

- Added a custom field component that dynamically computes available
options based on:
  - Current filters from `useListQuery`
  - Selection state from `useSelection`
- Injected the dynamic `field` prop into `RadioGroupField` to enable
rendering.
- Ensured the `where` field updates automatically in sync with the
selected radio.
- Added `isWhereEmpty` utility to avoid showing `currentFilters` when
`query.where` contains no meaningful conditions (e.g. `{ or: [] }`).
2025-07-09 15:44:22 -04:00
Alessio Gravili
e99c67f5f9 fix: ensure we perform ssrf check within dispatcher (#13078)
Previously, we were performing this check before calling the fetch
function. This changes it to perform the check within the dispatcher.

It adjusts the int tests to both trigger the dispatcher lookup function
(which is only triggered when not already passing a valid IP) and the
check before calling fetch

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210733180484570
2025-07-09 12:42:26 -07:00
Patrik
1c6a79bb57 fix(plugin-import-export): sync field select dropdown with form value (#13103)
### What?

Fixes a sync issue between the "Fields to Export" `<ReactSelect />`
dropdown and the underlying form state in the import-export plugin.

### Why?

Previously, the dropdown displayed outdated selections until an extra
click occurred. This was caused by an unnecessary `useState`
(`displayedValue`) that fell out of sync with the `useField` form value.

### How?

- Removed the separate `displayedValue` state
- Derived the selected values directly from the form field value using
inline mapping
2025-07-09 15:42:06 -04:00
Germán Jabloñski
a7a05012fb feat(next): add redirect from ${adminRoute}/collections to ${adminRoute} (#13061)
Occasionally, I find myself on a URL like
`https://domain.com/admin/collections/myCollection/docId` and I modify
the URL with the intention of going to the admin panel, but I shorten it
in the wrong place: `https://domain.com/admin/collections`.

The confusion arises because the admin panel basically displays the
collections.

I think this redirect is a subtle but nice touch, since `/collections`
is a URL that doesn't exist.

EDIT: now I'm doing also the same thing for `/globals`
2025-07-09 10:39:02 -04:00
Said Akhrarov
1d6ffcb80e feat(ui): adds support for copy pasting complex fields (#11513)
<!--

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 introduces support for copy + pasting complex fields such as
Arrays and Blocks. These changes introduce a new `ClipboardAction`
component that houses logic for copy + pasting to and from the clipboard
to supported fields. I've scoped this PR to include only Blocks &
Arrays, however the structure of the components introduced lend
themselves to be easily extended to other field types. I've limited the
scope because there may be design & functional blockers that make it
unclear how to add actions to particular fields.

Supported fields:
- Arrays
([Demo](https://github.com/user-attachments/assets/523916f6-77d0-43e2-9a11-a6a9d8c1b71c))
- Array Rows
([Demo](https://github.com/user-attachments/assets/0cd01a1f-3e5e-4fea-ac83-8c0bba8d1aac))
- Blocks
([Demo](https://github.com/user-attachments/assets/4c55ac2b-55f4-4793-9b53-309b2e090dd9))
- Block Rows
([Demo](https://github.com/user-attachments/assets/1b4d2bea-981a-485b-a6c4-c59a77a50567))

Fields that may be supported in the future with minimal effort by
adopting the changes introduced here:
- Tabs
- Groups
- Collapsible
- Relationships

This PR also encompasses e2e tests that check both field and row-level
copy/pasting.

### Why?
To make it simpler and faster to copy complex fields over between
documents and rows within those docs.

### How?
Introduces a new `ClipboardAction` component with helper utilities to
aid in copy/pasting and validating field data.

Addresses #2977 & #10703

Notes:
- There seems to be an issue with Blocks & Arrays that contain RichText
fields where the RichText field dissappears from the dom upon replacing
form state. These fields are resurfaced after either saving the data or
dragging/dropping the row containing them.
- Copying a Row and then pasting it at the field-level will overwrite
the field to include only that one row. This is intended however can be
changed if requested.
- Clipboard permissions are required to use this feature. [See Clipboard
API caniuse](https://caniuse.com/async-clipboard).

#### TODO
- [x] ~~I forgot BlockReferences~~
- [x] ~~Fix tests failing due to new buttons causing locator conflicts~~
- [x] ~~Ensure deeply nested structures work~~
- [x] ~~Add missing translations~~
- [x] ~~Implement local storage instead of clipboard api~~
- [x] ~~Improve tests~~

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-07-09 13:59:22 +00:00
Elliot DeNolf
dde9681089 ci: use .tool-versions file in setup (#13093)
Parse `.tool-versions` file in the composite node setup action. This
will make it the source of truth and easier to bump node/pnpm versions
in the future.
2025-07-08 21:20:31 +00:00
Elliot DeNolf
c876ddf858 ci: audit-dependencies workflow (#13090)
Add weekly check for dependency vulnerabilities.

Asana:
https://app.asana.com/1/10497086658021/project/1210456585958356/task/1210561338171143
2025-07-08 14:42:24 -04:00
Jarrod Flesch
855a320474 fix: ensure default values are not shown when value is hidden (#13074)
Fixes #12834 

`loginAttempts` was being shown in the admin panel when it should be
hidden. The field is set to `hidden: true` therefore the value is
removed from siblingData and passes the `allowDefaultValue` check -
showing inconsistent data.

This PR ensures the default value is not returned if the field has a
value but was removed due to the field being hidden.
2025-07-08 13:34:10 -04:00
Jarrod Flesch
aa97f3cddb fix: correctly reset login attempts (#13075)
Login attempts were not being reset correctly which led to situations
where a failed login attempt followed by a successful login attempt
would keep the loginAttempts at 1.


### Before 
Example with maxAttempts of 2:
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 1`
- failed login -> `loginAttempts: 2`
- successful login -> `"This user is locked due to having too many
failed login attempts."`

### After 
Example with maxAttempts of 2:
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 0`
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 0`
2025-07-08 13:32:16 -04:00
Jacob Fletcher
0b88466de6 fix(next): prevent live preview url functions from firing unnecessarily (#13088)
Ensures Live Preview url functions aren't fired during create or on
collections that do not have Live Preview enabled.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210743577153852
2025-07-08 13:02:54 -04:00
Elliot DeNolf
fee33b59ee ci: blank audit-dependencies workflow [skip ci] 2025-07-08 12:28:53 -04:00
Elliot DeNolf
417b70e16c chore(deps): bump deps to resolve all high severity (#13002)
Bumps dependencies to resolve all `high` severity vulnerabilities
2025-07-08 11:42:41 -04:00
Jessica Rynkar
9f1bff57c1 feat: exports new sanitizeUserDataForEmail function (#13029)
### What?

Adds a new `sanitizeUserDataForEmail` function, exported from
`payload/shared`.
This function sanitizes user data passed to email templates to prevent
injection of HTML, executable code, or other malicious content.

### Why?

In the existing `email` example, we directly insert `user.name` into the
generated email content. Similarly, the `newsletter` collection uses
`doc.name` directly in the email content. A security report identified
this as a potential vulnerability that could be exploited and used to
inject executable or malicious code.

Although this issue does not originate from Payload core, developers
using our examples may unknowingly introduce this vulnerability into
their own codebases.

### How?

Introduces the pre-built `sanitizeUserDataForEmail` function and updates
relevant email examples to use it.

**Fixes `CMS2-1225-14`**
2025-07-08 12:47:34 +01:00
Dani Calero
4c25357831 fix(ui): improve alignment of clear and dropdown indicator buttons in select based fields (#12995) 2025-07-08 07:06:46 -04:00
Jessica Rynkar
8a5cb27463 fix(ui): prevent error crashing UI when relationship assigned as useAsTitle (#12981)
### What?

- Updates the `RenderTitle` component to check that the `title` is a
string before returning it.
- Adds note to docs that **Relationship** and **Join** fields cannot be
assigned to `useAsTitle`, a **virtual** field should be used instead.

### Why?
When autosave is enabled and the `useAsTitle` points to a relationship
field, the autosave process returns an `object` for the title, this gets
passed to the `RenderTitle` component and throws an error which crashes
the UI.

### How?
Safely checks that `title` is a string before rendering it in
`RenderTitle` and updates docs to clarify that Relationship/Joins are
not compatible with `useAsTitle`.

Fixes #12960
2025-07-08 10:55:04 +01:00
Dan Ribbens
9c453210f8 fix: payload auth api-key algorithm compatibility (#13076)
When saving api-keys in prior versions you can have sha1 generated
lookup keys. This ensures compatibility with newer sha256 lookups.
2025-07-07 21:23:02 -04:00
Adam Klingbaum
96c24a22da docs(templates): fix grammar in README (#13027)
## Summary

Fixed a grammatical error in the README files for the website templates.

## Changes

- Fixed grammar in the on-demand revalidation section: changed "or
footer or header, change they will" to "footer, or header changes will"

## Files Changed

- `templates/website/README.md`
- `templates/with-vercel-website/README.md`

## Type of Change

- [x] Documentation fix/improvement
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change

This fixes a typo that was making the sentence grammatically incorrect
and hard to read.
2025-07-07 17:41:44 -04:00
Elliot DeNolf
14612b4db8 chore(release): v3.46.0 [skip ci] 2025-07-07 16:10:10 -04:00
Patrik
e6f8ca6fd0 fix: deduplicate custom array id fields (#13064)
When adding a custom ID field to an array's config, both the default
field provided by Payload, and the custom ID field, exist in the
resulting config. This can lead to problems when the looking up the
field's config, where either one or the other will be returned.

Fixes #12978
2025-07-07 13:06:31 -07:00
Kendell
ba660fdea2 feat: adds restricted file check (#12989)
Adds `restrictedFileTypes` (default: `false`) to upload collections
which prevents files on a restricted list from being uploaded.

To skip this check:
- set `[Collection].upload.restrictedFileTypes` to `true`
- set `[Collection].upload.mimeType` to any type(s)
2025-07-07 16:04:34 -04:00
Alessio Gravili
af9837de44 ci: analyze bundle size (#13071)
This adds a new `analyze` step to our CI that analyzes the bundle size
for our `payload`, `@payloadcms/ui`, `@payloadcms/next` and
`@payloadcms/richtext-lexical` packages.

It does so using a new `build:bundle-for-analysis` script that packages
can add if the normal build step does not output an esbuild-bundled
version suitable for analyzing. For example, `ui` already runs esbuild,
but we run it again using `build:bundle-for-analysis` because we do not
want to split the bundle.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210692087147570
2025-07-07 20:00:02 +00:00
Jessica Rynkar
f4f13a26c7 fix(next): adds token to reset password initialState (#13067)
### What?
Adds `token` into the `initialState` for the reset password form to
ensure `token` does not get passed through as `undefined`.

### Why?
Currently the reset password UI is broken because `token` is not getting
passed to the form state.

### How?
Adds `token` to `initialState`

Fixes #13040
2025-07-07 19:09:11 +00:00
Jarrod Flesch
6d5cc843a2 fix(db-mongodb): updateOne mutates the data object and does not transform it for read (#13065)
Fixes https://github.com/payloadcms/payload/issues/13045

`updateOne` when returning is `false` mutates the data object for write
operations on the DB, but that causes an issue when using that data
object later on since all of the id's are mutated to objectIDs and never
transformed back into read id's.

This fix ensures that the transform happens even when the result is not
returned.
2025-07-07 14:50:01 -04:00
Jarrod Flesch
34920a7ec0 test: fix tests that rely on remote urls (#13073) 2025-07-07 14:02:55 -04:00
Germán Jabloñski
2650eb7d44 fix(ui): increase timeout for opening list drawer in RelationshipInput (#13031)
As stated in #12529, the setTimeout was defined through trial and error
as it wasn't possible to reproduce the bug with the devtools open and
therefore with the CPU throttled. One user reported still experiencing
the bug.

I'm increasing the timeout to 100ms, which seems acceptable enough to
keep postponing a better fix, considering the bug isn't that critical.

If we find it keeps happening, we'll probably need to investigate the
root cause.
2025-07-07 09:30:09 -04:00
Jessica Rynkar
50c2f8bec2 fix(plugin-redirects): make 'from' field unique to prevent errors in redirect logic (#12964)
### What?
This PR updates the `from` field in `plugin-redirects` to add `unique:
true`.

### Why?
If you create multiple redirects with the same `from` URL — the
application won't know which one to follow, which causes errors and
unpredictable behavior.

### How?
Adds `unique: true` to the plugin injected `from` field.

### Migration Required
This change will require a migration. Projects already using this plugin
will need to:
- Ensure there are no duplicate `from` values in their existing
redirects collection.
- Remove or modify any duplicate entries before applying this update.

Fixes #12959
2025-07-07 11:21:40 +01:00
Jacob Fletcher
f49eeb1a63 fix(next): respect collection-level live preview config (#13036)
Fixes #13035.

We broke collection-level live preview configs in #12860.
2025-07-03 21:47:16 +00:00
Jarrod Flesch
1d9ad6f2f1 fix(ui): change password button is hidden when user has full field access (#12988) 2025-07-03 13:59:22 -04:00
Kendell
30fc7e3012 fix: check hostname of upload url (#13018)
Adds:
```ts
import { lookup } from 'dns/promises'
// ...
const { address } = await lookup(hostname)
// ...
return isSafeIp(address)
```

To ensure that an `ip` address is being verified. Previously, hostnames
were being verified by `isSafeIp`.


Fixes: https://github.com/payloadcms/payload/issues/12876
2025-07-03 10:50:31 -04:00
Elliot DeNolf
1ccd7ef074 chore(release): v3.45.0 [skip ci] 2025-07-03 09:23:23 -04:00
Patrik
34c3a5193b fix(plugin-import-export): pre-scan columns before streaming CSV export (#13009)
### What?

Fixes an issue where only the fields from the first batch of documents
were used to generate CSV column headers during streaming exports.

### Why?

Previously, columns were determined during the first streaming batch. If
a field appeared only in later documents, it was omitted from the CSV
entirely — leading to incomplete exports when fields were sparsely
populated across the dataset.

### How?

- Adds a **pre-scan step** before streaming begins to collect all column
keys across all pages
- Uses this superset of keys to define the final CSV header
- Ensures every row is padded to match the full column set

This matches the behavior of non-streamed exports and guarantees that
the streamed CSV output includes all relevant fields, regardless of when
they appear in pagination.
2025-07-03 08:53:02 -04:00
Sasha
81532cb9c9 fix(db-mongodb): nested sorting by ID (#13016)
Fixes sorting when the `sort` path contains a relationship and ends with
`id`, for example `sort: 'post.category.id'`.
2025-07-03 08:51:45 -04:00
Sebastian Blank
f70c6fe3e7 fix(templates): wrong link in demo content (custom components) (#13024)
### What?

The "custom component" link in the dashboard of the website demo is
wrong:

![image](https://github.com/user-attachments/assets/ee716a87-c515-4561-932d-f1c1fcccfd5e)
2025-07-03 12:07:19 +00:00
Alessio Gravili
e6b664284f chore: fix payload bundle script (#13022)
This fixes the payload bundle script. While not run by default, it's
useful for checking the payload bundle size by manually running `cd
packages/payload && node bundle.js`.
2025-07-03 04:37:44 -07:00
Alessio Gravili
fafaa04e1a fix(drizzle): ensure updateOne does not create new document if where query has no results (#12991)
Previously, `db.updateOne` calls with `where` queries that lead to no
results would create new rows on drizzle. Essentially, `db.updateOne`
behaved like `db.upsertOne` on drizzle
2025-07-02 13:56:59 -07:00
Germán Jabloñski
babcd599da fix(ui): save nested richtext inside inlineBlock (#12773)
Removing the `setTimeout` not only doesn't break any tests, but it also
fixes the linked issue.

The long comment above the if statement was added in
https://github.com/payloadcms/payload/pull/5460 and explains why the if
statement is necessary GIVEN the existence of the `setTimeout`, but the
`setTimeout` was introduced [earlier because the button apparently
didn't work](https://github.com/payloadcms/payload/issues/1414).

It seems to work now without the `setTimeout`, because otherwise the
tests wouldn't even pass. I also tested it manually, and it works fine.


Fixes #12687
2025-07-02 19:43:48 +00:00
Jessica Rynkar
ac19b78968 style(richtext-lexical): ensure error state is shown at small-break (#12827)
### What?
Shows error state (red left border) on small screens.

### Why?
The current error state disappears at small-break screen width.

### How?
Updates small-break error state to match the desktop error state for the
Lexical field.

##### Reported by client.
2025-07-02 12:16:50 -07:00
Jacob Fletcher
b40c581a27 fix(ui): autosave infinite loop within document drawer (#13007)
Required for #13005.

Opening an autosave-enabled document within a drawer triggers an
infinite loop when the root document is also autosave-enabled.

This was for two reasons:

1. Autosave would run and change the `updatedAt` timestamp. This would
trigger another run of autosave, and so on. The timestamp is now removed
before comparison to ensure that sequential autosave runs are skipped.

2. The `dequal()` call was not being given the `.current` property off
the ref object. This meant that is was never evaluate to `true` and
therefore never skip unnecessary autosaves to begin with.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210697235723932
2025-07-02 15:11:38 -04:00
Patrik
335af1b8c9 fix(plugin-import-export): preview table to include all selected columns regardless of populated data (#12985)
### What?

Ensure the export preview table includes all field keys as columns, even
if those fields are not populated in any of the returned documents.

### Why?

Previously, if none of the documents in the preview result had a value
for a given field, that column would be missing entirely from the
preview table.

### How?

- Introduced a `getFlattenedFieldKeys` utility that recursively extracts
all missing flattened field accessors from the collection’s config that
are undefined

- Updates the preview UI logic to build columns from all flattened keys,
not just the first document
2025-07-02 09:28:21 -07:00
Alessio Gravili
583a733334 feat(drizzle): support half-precision, binary, and sparse vectors column types (#12491)
Adds support for `halfvec` and `sparsevec` and `bit` (binary vector)
column types. This is required for supporting indexing of embeddings >
2000 dimensions on postgres using the pg-vector extension.
2025-07-02 19:24:53 +03:00
Jessica Rynkar
6e5ddc8873 fix(examples): only allow super admins to create users with super admin role (#13015)
### What?

This PR updates the `create` access control on the `users` collection in
the `multi-tenant` example to prevent unauthorized creation of
`super-admin` users.

### Why?

Previously, any authenticated user could create a new user and assign
them the `super-admin` role — even if they didn’t have that role
themselves. This bypassed role-based restrictions and introduced a
security vulnerability, allowing users to escalate their own privileges
by working around role restrictions during user creation.

### How?

The `create` access function now checks whether the current user has the
`super-admin` role before allowing the creation of another
`super-admin`. If not, the request is denied.


**Fixes:** `CMS2-Q225-01`
2025-07-02 15:42:55 +01:00
Jarrod Flesch
9ba740e472 fix(ui): field bulk upload showing stale data (#13006) 2025-07-02 10:11:51 -04:00
Jessica Rynkar
50029532aa fix(examples): checks requested tenant matches user tenant permissions (#13012)
### What

This PR updates the `create` access control functions in the
`multi-tenant` example to ensure that any `tenant` specified in a create
request matches a tenant the user has admin access to.

### Why

Previously, while the admin panel UI restricted the tenant selection, it
was still possible to bypass this by making a request directly to the
API with a different `tenant`. This allowed users to create documents
under tenants they shouldn't have access to.

### How

The `access` functions on the `users` and `pages` collections now
explicitly check whether the tenant(s) in the request are included in
the user's tenant permissions. If not, access is denied by returning
`false`.

**Fixes: CMS2-Q225-03**
2025-07-02 14:30:47 +01:00
Jacob Fletcher
c80b6e92c4 fix(ui): prevent document drawer from remounting on save (#13005)
Supersedes #12992. Partially closes #12975.

Right now autosave-enabled documents opened within a drawer will
unnecessarily remount on every autosave interval, causing loss of input
focus, etc. This makes it nearly impossible to edit these documents,
especially if the interval is very short.

But the same is true for non-autosave documents when "manually" saving,
e.g. pressing the "save draft" or "publish changes" buttons. This has
gone largely unnoticed, however, as the user has already lost focus of
the form to interact with these controls, and they somewhat expect this
behavior or at least accept it.

Now, the form remains mounted across autosave events and the user's
cursor never loses focus. Much better.

Before:


https://github.com/user-attachments/assets/a159cdc0-21e8-45f6-a14d-6256e53bc3df

After:


https://github.com/user-attachments/assets/cd697439-1cd3-4033-8330-a5642f7810e8

Related: #12842

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210689077645986
2025-07-02 09:07:08 -04:00
Jarrod Flesch
a9580e05ac fix: disable graphql introspection queries when disableIntrospectionInProduction is true (#12982) 2025-07-02 08:33:20 -04:00
Jarrod Flesch
57d00ad2e9 test: reduce queue test amount (#13008) 2025-07-01 15:55:16 -04:00
Jarrod Flesch
a9ad7c771e fix(ui): bulk upload redirecting to relationship documents when added (#13001)
Fixes https://github.com/payloadcms/payload/issues/12786
2025-07-01 15:23:11 -04:00
Patrik
7a40a9fc06 fix(ui): skip disabled fields when adding OR filter conditions in list view (#13004)
### What?

Fixes a bug where adding an additional OR filter condition in the list
view selects a field with `admin.disableListFilter: true`, causing all
filter fields to appear disabled.

### Why?

When the first field in a collection has `disableListFilter` set to
`true`, adding a second OR condition defaults to using that field. This
leads to a broken filter UI where no valid fields are selectable.

### How?

Replaces the hardcoded usage of `reducedFields[0]` with a call to
`reducedFields.find(...) `that skips fields with `disableListFilter:
true`, consistent with the logic already used when adding the first
filter condition.

Fixes #12993
2025-07-01 11:35:48 -07:00
Patrik
b1ae749311 fix(ui): render preview sizes button when adjustments are disabled but image sizes are defined (#12999)
### What?

The "Preview Sizes" button in the file upload UI was not showing up if:
- `crop` and `focalPoint` were both `false`
- No `customUploadActions` were provided
- But image sizes were configured

### Why?

This happened because `UploadActions` wasn’t rendered at all unless
adjustments or custom actions were present.

### How?

Update the conditional in `StaticFileDetails` to also render
`UploadActions` when:
- `hasImageSizes` is `true` and the document has a `filename`

Fixes #12832
2025-07-01 07:44:48 -07:00
Jacob Fletcher
3f30a2e300 fix(ui): block rows unexpectedly collapse and array rows not collapsed on init (#12987) 2025-06-30 21:12:26 -04:00
Jarrod Flesch
c07187d804 test: fix multi-tenant flakes (#12983) 2025-06-30 17:18:41 -04:00
Sasha
0e8ac0bad5 fix(db-postgres): joins with hasMany: true relationships nested to an array (#12980)
Fixes https://github.com/payloadcms/payload/issues/12679
2025-06-30 21:25:29 +03:00
Alessio Gravili
463c9754c7 templates: fix pnpm 10 ignored build scripts warning (#12974)
When using pnpm 10 to install any of our templates, the following
warning is thrown:

![Screenshot 2025-06-29 at 13 23
28@2x](https://github.com/user-attachments/assets/450630f1-0455-48a0-96e9-516110b6146c)

> Warning: Ignored build scripts: esbuild, unrs-resolver. Run "pnpm
approve-builds" to pick which dependencies should be allowed to run
scripts.

This PR fixes this by adding those packages to `onlyBuiltDependencies`
2025-06-29 15:17:34 -07:00
Alessio Gravili
4458f74cef ci: template errors not being caught due. fix: error due to updated generated-types User type (#12973)
This PR consists of two separate changes. One change cannot pass CI
without the other, so both are included in this single PR.


## CI - ensure types are generated

Our website template is currently failing to build due to a type error.
This error was introduced by a change in our generated types.

Our CI did not catch this issue because it wasn't generating types /
import map before attempting to build the templates. This PR updates the
CI to generate types first.

It also updates some CI step names for improved clarity.

## Fix: type error

![Screenshot 2025-06-29 at 12 53
49@2x](https://github.com/user-attachments/assets/962f1513-bc6c-4e12-9b74-9b891c49900b)


This fixes the type error by ensuring we consistently use the _same_
generated `TypedUser` object within payload, instead of `BaseUser`.
Previously, we sometimes used the generated-types user and sometimes the
base user, which was causing type conflicts depending on what the
generated user type was.

It also deprecates the `User` type (which was essentially just
`BaseUser`), as consumers should use `TypedUser` instead. `TypedUser`
will automatically fall back to `BaseUser` if no generated types exists,
but will accept passing it a generated-types User.

Without this change, additional properties added to the user via
generated-types may cause the user object to not be accepted by
functions that only accept a `User` instead of a `TypedUser`, which is
what failed here.

## Templates: re-generate templates to update generated types

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210668927737258
2025-06-29 14:27:50 -07:00
Jacob Fletcher
cfc7adcbc5 fix: strict custom view paths (#12968) 2025-06-29 14:20:54 -04:00
Jarrod Flesch
16f5538e12 fix(plugin-multi-tenant): unnecessary modal appearing (#12854)
Fixes #12826 

Leave without saving was being triggered when no changes were made to
the tenant. This should only happen if the value in form state differs
from that of the selected tenant, i.e. after changing tenants.

Adds tenant selector syncing so the selector updates when a tenant is
added or the name is edited.

Also adds E2E for most multi-tenant admin functionality. 

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210562742356842
2025-06-27 16:30:13 -04:00
Said Akhrarov
9f6030641a fix: appropriately throw unverified email error (#12933)
<!--

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 addresses an issue where the order of operations/conditions for
throwing an unverified email error were incorrect.

### Why?
To properly throw an unverified email error under the correct
conditions.

### How?
Pushing this error to be thrown later in the operation.
2025-06-27 19:26:37 +00:00
Jacob Fletcher
f2213e5c5c feat: mount live preview to document root (#12860)
Mounts live preview to `../:id` instead `../:id/preview`.

This is a huge win for both UX and a maintainability standpoint.

Here are just a few of those wins:

1. If you edit a document, _then_ decide you want to preview those
changes, you are currently presented with the `LeaveWithoutSaving` modal
and are forced to either save your edits or clear them. This is because
you are being navigated to an entirely new page with it's own form
context. Instead, you should be able to freely navigate back and forth
between the two.
2. If you are an editor who most often uses Live Preview, or you are
editing a collection that typically requires it, you likely want it to
automatically enter live preview mode when you open a document.
Currently, the user has to navigate to the document _first_, then use
the live preview tab. Instead, you should be able to set a preference
and avoid this extra step.
3. Since the inception of Live Preview, we've been maintaining largely
the same code across the default edit view and the live preview view,
which often became out of sync and inconsistent—but they're essentially
doing the same thing. While we could abstract a lot of this out, it is
no longer necessary if the two views are combined into one.

This change does also include some small modifications to UI. The "Live
Preview" tab no longer exists, and instead has been replaced with a
button placed next to the document controls (subject to change).

Before:


https://github.com/user-attachments/assets/48518b02-87ba-4750-ba7b-b21b5c75240a

After:


https://github.com/user-attachments/assets/a8ec8657-a6d6-4ee1-b9a7-3c1173bcfa96
2025-06-27 11:58:00 -04:00
Jessica Rynkar
6f6d305f9d fix(ui): prevent error if rows is undefined in mergeServerFormState (#12962)
### What? 
Adds optional chaining when accessing `rows` in `mergeServerFormState`
to prevent error crashing the UI.

### Why? 
When an array field is populated in a `beforeChange` hook and was
previously empty, it crashes `mergeServerFormState.ts` on this line
because no `rows` exist:

```ts 
const indexInCurrentState = currentState[path].rows.findIndex
``` 

The line after this checks `if (indexInCurrentState > -1)` so returning
undefined here will not affect the subsequent code.

### How? 
Added optional chaining to the access of `rows`, which prevents the
error being thrown.

Fixes #12944
2025-06-27 15:57:48 +00:00
Paul
c902f14cb3 fix(db-mongodb): add ability to disable fallback sort and no longer adds a fallback for unique fields (#12961)
You can now disable fallback sort in the mongodb adapter by passing
`disableFallbackSort: true` in the options.

We also no longer add fallback sort to sorts on unique fields by default
now.

This came out of a discussion in this issue
https://github.com/payloadcms/payload/issues/12690
and the linked PR https://github.com/payloadcms/payload/pull/12888

Closes https://github.com/payloadcms/payload/issues/12690
2025-06-27 13:45:30 +00:00
Elliot DeNolf
c66e5ca823 chore(release): v3.44.0 [skip ci] 2025-06-27 09:23:04 -04:00
James Mikrut
26d709dda6 feat: auth sessions (#12483)
Adds full session functionality into Payload's existing local
authentication strategy.

It's enabled by default, because this is a more secure pattern that we
should enforce. However, we have provided an opt-out pattern for those
that want to stick to stateless JWT authentication by passing
`collectionConfig.auth.useSessions: false`.

Todo:

- [x] @jessrynkar to update the Next.js server functions for refresh and
logout to support these new features
- [x] @jessrynkar resolve build errors

---------

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Co-authored-by: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com>
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-06-27 09:13:52 -04:00
Jacob Fletcher
c8b72141e4 feat: collection-level preferences (#12909)
Needed for #12860.

The new live preview pattern requires collection-level preferences, a
pattern that does not yet exist.

Instead of creating a new record for these types of preferences, we can
simply reuse `<collectionSlug>-list` under a more general key:
`collection-<slug>`. This way other relevant properties can be attached
in the future that might not specifically apply to the list view.

This will also match the conventions already estalished by
document-level preferences in `collection-<slug>-<id>` and
`global-<slug>`.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210628212784050
2025-06-27 09:08:47 -04:00
Elliot DeNolf
1db06195c2 ci: bring back CODEOWNERS file for reviews, approval not required [skip ci] 2025-06-27 09:00:52 -04:00
Roman
6a935d4d4d examples: fix broken navigation to post in localization example (#12810)
This pull request updates the `Card` component in the localization
example to support localized URLs. The most significant changes include
importing a new hook for locale management and modifying the URL
generation logic to include the locale.

Localization updates:

*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415R5):
Imported `useLocale` from `next-intl` to manage locale-based
functionality.
*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415R20):
Added a `locale` constant using the `useLocale` hook to retrieve the
current locale.
*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415L28-R30):
Updated the `href` generation logic to include the locale in the URL
structure, ensuring localized navigation.
2025-06-27 11:11:16 +00:00
Jesper We
c3c1614fa6 fix(ui): usePreventLeave should not show alert for exceptions (#12722)
When using 3rd party custom components in an edit form there exists a
possibility that a non-navigational click event will propagate through
to payload.

In this case the `findClosestAnchor` function in `usePreventLeave` may
find an anchor without href, resulting in the `newUrlObj = new
URL(newUrl)` in `isAnchorOfCurrentUrl` throwing the exception:

> TypeError: URL constructor:  is not a valid URL.

As a result a native alert is shown to the user, with no real
explanation as to what is going on. This is not a good experience.

I suggest moving it to a console log which is less "in your face" for
users who do not know what to do about it anyway.

I discovered this while using a data grid component with a context menu.
Clicking on menu items (which are `<a>` tags without href in this
component) triggers the error.

(Another on-liner fix would ofc be to not attempt to create an URL
object if there is no href `if (anchor?.href) {`, but I opted for this
version since using `alert()` in production code is not a preferred
practice anyway)

<!--

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 #

-->
2025-06-27 07:00:04 -04:00
ThijsAtFreave
e7695502e3 fix: richTextField supports beforeInput/afterInput, but these were missing from types.ts (#12889)
Add `afterInput` and `beforeInput` to `admin.components` of
RichTextField type. These props are supported but missing from types.
2025-06-27 06:50:04 -04:00
Sam Wheeler
0e9865c564 fix(ui): vertically align table headers to the middle (#12699)
This fixes a small ui bug where the items in the table header were not
vertically aligned when they don't contain the SortColumn component. The
SortColumn component handles vertical alignment with a nested flexbox.
The PR adds vertical-align: middle directly to the th element so that
the text in the header is vertically aligned even when there isn't a
nested flexbox

Before:
<img width="719" alt="Screenshot 2025-06-05 at 10 24 19 AM"
src="https://github.com/user-attachments/assets/3962517e-3b22-452a-af04-8397549c4ed9"
/>

After:
<img width="719" alt="Screenshot 2025-06-05 at 10 30 39 AM"
src="https://github.com/user-attachments/assets/0c5a0847-8ee2-4439-981e-f3538908e920"
/>
2025-06-27 06:43:41 -04:00
Chandler Gonzales
e5e0ec86c5 docs: remove group from list of default field validations (#12921)
### What?

Removes group from the list of default field validations in the docs

### Why?

It doesn't exist in the code:
886c07e918/packages/payload/src/fields/validations.ts
2025-06-27 06:34:22 -04:00
Jessica Rynkar
c76d83985d fix(plugin-multi-tenant): updates tenant selector upon tenant creation (#12936)
### What?
Updates the tenant selector displayed in the sidebar when a new tenant
is created.

### Why?
Currently when using the multi-tenant plugin and creating a new tenant
doc, the tenant selector dropdown does not display the new tenant as an
option until the page gets refreshed.

### How?
Extends the `WatchTenantCollection` helper to check if the tenant `id`
from the current doc exists, if the tenant is new it manually calls
`updateTenants`. The `updateTenants` function previously only adjusted
the title on existing tenants, this has been updated to add a new tenant
as an option when it doesn't exist.

#### Reported by client
2025-06-27 06:26:05 -04:00
Said Akhrarov
a1822d21d0 fix(ui): properly render create new button in polymorphic joins (#12930)
<!--

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 fixes an issue where the bottom "Create new ..." button would
cause a runtime error due to not accounting for a polymorphic join
setup.

### Why?
To prevent a runtime error and allow users the ability to add new
documents to the join as expected even in a polymorphic setup.

### How?
Creation of a new `AddNewButton` which handles all of the add new button
instances in the `RelationshipTable` component.

Addresses
https://github.com/payloadcms/payload/issues/12913#issuecomment-3001475438

Before:


[join-polymorphic-runtime-error--Payload.webm](https://github.com/user-attachments/assets/fad3a1ba-c51c-4731-84cc-c27adbaac1d9)


After:

[polymorphic-after-Editing---Multiple-Collections-Parent---Payload
(1).webm](https://github.com/user-attachments/assets/e3baf902-1b2b-4f19-8b6d-838edd6fef80)
2025-06-27 05:47:36 -04:00
Dani Calero 🚀
4b9566f8b8 fix(ui): render DateTime label as <label> instead of <span> (#12949)
## What / Why
Date & Time fields were rendering their field label as a `<span>` while
every other field type uses a proper `<label>` with a matching
`htmlFor`.

Because the element was a span it broke styles and made 'field-label'
have different styles from the rest of 'field-label's.

**Root cause:** DateTimeField failed to pass its `path` (or an explicit
`htmlFor`) to `FieldLabel`. When `FieldLabel` receives no `htmlFor`, it
intentionally downgrades to a `<span>`.

## Screenshots

### Before

![image](https://github.com/user-attachments/assets/edecfce7-0326-4f3e-af76-d7b37158343a)
*DateTime label rendered as `<span>`, causing style inconsistencies*

### After  

![image](https://github.com/user-attachments/assets/d9fb06c2-1ca0-4f8d-803d-15c6c6355d1e)
*DateTime label now rendered as proper `<label>` element*

## Changes introduced
- `packages/ui/src/fields/DateTime/index.tsx`
  - Added `path={path}` prop to `FieldLabel` component

## Behavior after the fix
- Date-time labels are now real `<label>` elements with `for="field-…"`
- Visual alignment now matches every other field type  

## How to test manually
1. Run `pnpm dev fields`
2. Inspect the DateTime field markup – label is now `<label>` 
3. Observe that vertical spacing matches other types of fields
2025-06-27 05:34:28 -04:00
Sasha
54afaf9529 fix(db-mongodb): strip deleted from the config blocks from the result (#12869)
If you (using the MongoDB adapter) delete a block from the payload
config, but still have some data with that block in the DB, you'd
receive in the admin panel an error like:
```
Block with type "cta" was found in block data, but no block with that type is defined in the config for field with schema path pages.blocks
```

Now, we remove those "unknown" blocks at the DB adapter level.

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-06-27 05:30:48 -04:00
Patrik
3830d710a4 feat(plugin-import-export): preview displays CSV and JSON data accurately (#12948)
### What

This PR updates the import-export plugin's `<Preview />` component to
render table columns and rows using the same logic as the CSV export.

Key changes:
- Adds a new `/api/preview-data` custom REST endpoint that:
  - Accepts filters (`fields`, `where`, `sort`, `draft`, `limit`)
- Uses `getCustomFieldFunctions` and `flattenObject` to transform
documents
  - Returns deeply flattened rows identical to the CSV export
- Refactors the <Preview /> component to:
- POST preview config to the new endpoint instead of querying the
collection directly
- Match column ordering and flattening logic with the `createExport`
function
- Ensures consistency across CSV downloads and in-admin previews
-Adds JSON preview

This ensures preview results now exactly match exported CSV content,
including support for custom field transformers and polymorphic fields.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-06-27 05:10:28 -04:00
Dave Ryan
2da6d924de fix: validate "null" value for point field as true when its not required (#12908)
### What?

This PR solves an issue with validation of the `point` field in Payload
CMS. If the value is `null` and the field is not required, the
validation will return `true` before trying to examine the contents of
the field

### Why?

If the point field is given a value, and saved, it is then impossible to
successfully "unset" the point field, either through the CMS UI or
through a hook like `beforeChange`. Trying to do so will throw this
error:

```
[17:09:41] ERROR: Cannot read properties of null (reading '0')
    err: {
      "type": "TypeError",
      "message": "Cannot read properties of null (reading '0')",
      "stack":
          TypeError: Cannot read properties of null (reading '0')
              at point (webpack-internal:///(rsc)/./node_modules/.pnpm/payload@3.43.0_graphql@16.10.0_typescript@5.7.3/node_modules/payload/dist/fields/validations.js:622:40)
```

because a value of `null` will not be changed to the default value of
`['','']`, which in any case does not pass MongoDB validation either.

```
[17:22:49] ERROR: Cast to [Number] failed for value "[ NaN, NaN ]" (type string) at path "location.coordinates.0" because of "CastError"
    err: {
      "type": "CastError",
      "message": "Cast to [Number] failed for value \"[ NaN, NaN ]\" (type string) at path \"location.coordinates.0\" because of \"CastError\"",
      "stack":
          CastError: Cast to [Number] failed for value "[ NaN, NaN ]" (type string) at path "location.coordinates.0" because of "CastError"
              at SchemaArray.cast (webpack-internal:///(rsc)/./node_modules/.pnpm/mongoose@8.15.1_@aws-sdk+credential-providers@3.778.0/node_modules/mongoose/lib/schema/array.js:414:15)
```


### How?

This adds a check to the top of the `point` validation function and
returns early before trying to examine the contents of the point field

---------

Co-authored-by: Dave Ryan <dmr@Daves-MacBook-Pro.local>
2025-06-27 07:49:47 +00:00
Jarrod Flesch
86e48ae70b test: bulk edit flaky selectors (#12950)
https://github.com/payloadcms/payload/pull/12861 introduced some flaky
test selectors. Specifically bulk editing values and then looking for
the previous values in the table rows.

This PR fixes the flakes and fixes eslint errors in `findTableRow` and
`findTableCell` helper funcitons.
2025-06-26 22:40:19 -04:00
Kendell
7ebac630f7 test: adds test for skipSafeFetch allowList (#12954)
Adds missing test in PR: #12927
2025-06-26 17:14:49 -04:00
Ondřej Závodný
7472798808 fix(live-preview): client-side live preview cannot populate more than 10 relationships at once (#12929)
### What?

Set the `limit` query param on API requests called within the
`useLivePreview` hook.

### Why?

We are heavily relying on the block system in our pages and we reuse the
media collection in a lot of the block types. When the page has more
than 10 images, the API request doesn't fetch all of them for live
preview due to the default 10 item `limit`. This PR allows the preview
page to override this `limit` so that all the items get correctly
fetched.

### Our current workaround

Set the `depth` param of `useLivePreview` hook like this:

```
useLivePreview({
  // ...
  depth: '1000&limit=1000',
})
```

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

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-26 16:36:49 -04:00
Said Akhrarov
605c993bb7 fix(drizzle): skip column if undefined in findMany (#12902)
<!--

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 fixes an issue where sorting on a traditional virtual field with
`virtual: true` while using a drizzle-based db adapter would cause a
runtime error.

### Why?
To skip attempting to sort virtual fields which are not linked to a
relationship/upload and prevent a runtime error from surfacing.

### How?
Skipping the deletion of the property from the `selectFields` object if
the column is false-y.

Fixes #12886

Before:


[sort-virtualfield-drizzle-error.mp4](https://private-user-images.githubusercontent.com/78685728/457602747-b8661e47-a1a8-4453-b2ec-b7e7199b9846.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTA2OTU0NzksIm5iZiI6MTc1MDY5NTE3OSwicGF0aCI6Ii83ODY4NTcyOC80NTc2MDI3NDctYjg2NjFlNDctYTFhOC00NDUzLWIyZWMtYjdlNzE5OWI5ODQ2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA2MjMlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNjIzVDE2MTI1OVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE3NmMzOWI5YjNiNzEwYzk3ZWUyNDllYTBjMzZkNzkzMjhjNzc5YzJhNDlkOTBiNDk5MDFhMTdmNDA4NjJhZWQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.N1GJsiI_gZ8M54VHCAmiPEhcJGqRw3Ucy-VeM5R7UFE)

After: 


[virtualfields-sort-Posts---Payload.webm](https://github.com/user-attachments/assets/f5a15d98-4a40-4817-bc6a-415f3ec27484)

<details>

<summary>Collection config used above</summary>

```ts
export const PostsCollection: CollectionConfig = {
  slug: postsSlug,
  admin: {
    useAsTitle: 'title',
    defaultColumns: ['title', 'exampleField'],
  },
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    {
      name: 'exampleField',
      type: 'text',
      virtual: true,
      admin: {
        readOnly: true,
      },
      hooks: {
        afterRead: [({ data }) => data?.title],
      },
    },
    {
      type: 'relationship',
      name: 'category',
      relationTo: 'categories',
    },
    {
      name: 'categoryTitle',
      type: 'text',
      virtual: 'category.title',
    },
  ],
}
```

</details>

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-06-26 19:52:29 +00:00
Kendell
a7ad573a0e fix: get external resource blocked (#12927)
## Fix
- Use `[Config].upload.skipSafeFetch` to allow specific external urls
- Use `[Config].upload.pasteURL.allowList` to allow specific external
urls

Documentation: [Uploading files from remote
urls](https://payloadcms.com/docs/upload/overview#uploading-files-from-remote-urls)

Fixes: https://github.com/payloadcms/payload/issues/12876
Mentioned: https://github.com/payloadcms/payload/issues/7037,
https://github.com/payloadcms/payload/issues/12934
Source PR: https://github.com/payloadcms/payload/pull/12622
Issue Trace:
1. [`allowList`
Added](8b7f2ddbf4 (diff-92acf7b8d30e447a791e37820136bcbf23c42f0358daca0fdea4e7b77f7d4bc9)
)

2. [`allowList`
Removed](648c168f86 (diff-92acf7b8d30e447a791e37820136bcbf23c42f0358daca0fdea4e7b77f7d4bc9))
2025-06-26 15:24:39 -04:00
Jarrod Flesch
d62d9b4b8e fix(ui): bulk upload losing state when adding additional files (#12946)
Fixes an issue where adding additional upload files would clear the
state of the originally uploaded files.
2025-06-26 15:23:38 -04:00
Jacob Fletcher
67fa5a0b3b fix(live-preview): foreign postMessage events reset client-side state (#12925)
Needed for #12860.

If the admin panel broadcasts foreign postMessage events, i.e. those
without the `payload-live-preview` signature, client-side live preview
subscriptions will reset back to initial state.

This is because we dispatch two postMessage events in the admin panel,
one for client-side live preview to catch (`payload-live-preview`), and
the other for server-side live preview (`payload-document-event`). This
was not previously noticeable because both events would only get called
simultaneously on initial render, where initial state is already the
expected result.

Now that Live Preview can be freely toggled on and off, both events are
frequently dispatched and very obviously disregard the current working
state.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210628466702818
2025-06-26 14:05:21 -04:00
Jacob Fletcher
bcb10b52b3 fix: restore missing properties to live preview client config (#12904)
Needed for #12860.

The client config unnecessarily omits the `livePreview.collections` and
`livePreview.globals` properties. This is because the root live preview
config extends the type with these two additional properties without
sharing it elsewhere. To led to the client sanitization function
overlooking these additional properties, as there was no type indication
that they exist.

The `collections` and `globals` properties are now appended to the
client config as expected, and the root live preview is standardized
behind the `RootLivePreviewConfig` type to ensure no properties are
lost.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210628466702823
2025-06-26 14:05:10 -04:00
Paul
87c7952558 feat(templates): added int and e2e tests to blank and website templates (#12866)
This PR adds int tests with vitest and e2e tests with playwright
directly into our templates.

The following are also updated:
- bumps core turbo to 2.5.4 in monorepo
- blank and website templates moved up to be part of the monorepo
workspace
- this means we now have thes templates filtered out in pnpm commands in
package.json
- they will now by default use workspace packages which we can use for
manual testing and int and e2e tests
  - note that turbo doesnt work with these for dev in monorepo context
- CPA script will fetch latest version and then replace `workspace:*` or
the pinned version in the package.json before installation
- blank template no longer uses _template as a base, this is to simplify
management for workspace
- updated the generate template variations script
2025-06-26 13:55:28 -04:00
Jacob Fletcher
141133a27f fix(next): live preview popup triggers leave without saving modal (#12947)
Partially closes #12121.

When you edit a document in Live Preview using the default iframe
window, then attempt to open the window as a popup, the
`LeaveWithoutSaving` modal will appear.

This is because the `usePreventLeave` hook watches for anchor tags that
might cause a page navigation, and rightfully warns the user before they
navigate away and lose their changes. The reason the popup button
triggers this hook is because it uses an anchor tag with an href for
accessibility, which fires events that are caught and processed by the
hook.

The fix is to add the `target="_blank"` attribute here so that the hook
understands that these events do not navigate the user away from the
page and can be ignored.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210643905956946
2025-06-26 16:59:20 +00:00
Ruby Jasmin
379fc127cc fix(ui): unreachable custom views when admin route set to '/' (#12812)
### What?
Fixes #12811

### Why?
Custom Views become unreachable when admin route is set to "/" because
the forward slash of the current route gets removed before routing to
custom view

### How?

Fixes #

-->

Fixes #12811

Custom Views become unreachable when admin route is set to "/" because
the forward slash of the current route gets removed before routing to
custom view

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

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-26 09:34:08 -04:00
Patrik
5cf92878a4 fix(plugin-import-export): duplicated rows and headers in CSV export when streaming paginated results (#12941)
This PR fixes an issue in the export logic where CSV downloads would
include duplicate rows and repeated column headers across paginated
batches.

Key changes:
- Ensured `page` is incremented correctly after each `payload.find` call
- Tracked and wrote CSV column headers only once for the first page
- Prevented row duplication by removing unused `result` initialization
and using isolated `page` tracking
- Streamlined both download and non-download logic for consistent batch
processing

This resolves incorrect row counts and header duplication in large CSV
exports.
2025-06-26 06:09:17 -07:00
Jarrod Flesch
8900a38678 fix: uses valid fractional index for test (#12942) 2025-06-26 06:40:18 -04:00
Paul
5368440115 chore: fix jest global teardown incorrectly always returning process exit status 0 (#12907)
We were running scripts as they were without encompassing our logic in a
function for jest's teardown and we were subsequently running
`process.exit(0)` which meant that tests didn't correctly return an
error status code when they failed in CI.

The following tests have been skipped as well:
```
  ● postgres vector custom column › should add a vector column and query it
  ● Sort › Local API › Orderable › should not break with existing base 62 digits
  ● Sort › Local API › Orderable join › should set order by default
  ● Sort › Local API › Orderable join › should allow setting the order with the local API
  ● Sort › Local API › Orderable join › should sort join docs in the correct
```

---------

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2025-06-25 17:43:57 -07:00
Said Akhrarov
9f17db8a7b fix(ui): toggle list selections off on successful bulk action (#12861)
### What?
This PR threads an onSuccess callback to bulk actions which get called
after a successful action. In this case, the callback toggles the list
selections off after a successful edit many, publish many, or unpublish
many.

### Why?
To ensure list selections are toggled off after a successful action.

### How?
By threading a new onSuccess callback through the actions' props.

Fixes #12855

Before


[12855-before.mp4](https://private-user-images.githubusercontent.com/65888/456602476-b327f0ba-c140-46be-8c71-7f6bfa74fd67.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTAyODQxMDEsIm5iZiI6MTc1MDI4MzgwMSwicGF0aCI6Ii82NTg4OC80NTY2MDI0NzYtYjMyN2YwYmEtYzE0MC00NmJlLThjNzEtN2Y2YmZhNzRmZDY3Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA2MTglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNjE4VDIxNTY0MVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTA0YTE4OTE5MjliZWQxNDM1OTU0ODlhMmY5ZjliNjhlODAyODU5ZmU3ODkzMjI1ODhiOTQyNmY0YzMyMGM0ZmQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.hzTLtuzltcpQUAIHYz7JoZ5x7JT4dPP9f-3c-GDf0Zc)

After


[Draft-Posts---Payload.webm](https://github.com/user-attachments/assets/474fbd9f-c7b3-46f4-ae31-5246cb22b86d)
2025-06-25 17:06:44 -04:00
Elliot DeNolf
b1a57fa350 chore: set trimTrailingWhitespace and insertFinalNewline in vscode settings (#12939)
Add the following to our vscode settings

```json
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
  ```
2025-06-25 11:08:10 -07:00
Sasha
c1f62972da fix(db-postgres): joins with custom schema (#12937)
Fixes normal and polymorphic joins when using a custom schema in
Postgres
2025-06-25 13:51:39 -04:00
Jessica Rynkar
c094b0e520 fix(ui): align caret on error tooltip for checkbox field (#12917)
### What?
Aligns the caret on the error message tooltip to the left when using a
checkbox field.

### Why?
All field error message tooltips have a right-aligned caret - when using
a checkbox field, this results in the caret pointing to open space (see
screenshots).

### How?
Left aligns the tooltip caret just for the checkbox field.

**Before:**
![Screenshot 2025-06-24 at 2 45
38 PM](https://github.com/user-attachments/assets/923f6a06-1f24-468d-88d8-12e3f0f0d27f)

**After:**
![Screenshot 2025-06-24 at 2 46
47 PM](https://github.com/user-attachments/assets/a2ebbe6a-2095-4295-9e94-320a1b943a6d)


#### Reported by client
2025-06-25 15:48:49 +00:00
Patrik
1cdec861cd test: guard against null values in custom toCSV functions (#12938)
### What?

Fixes a crash when exporting documents to CSV if a custom `toCSV`
function tries to access properties on a `null` value.

### Why?

In some cases (especially with Postgres), fields like relationships may
be explicitly `null` if unset. Custom `toCSV` functions that assume the
value is always defined would throw a `TypeError` when attempting to
access nested properties like `value.id`.

### How?

Added a null check in the custom `toCSV` implementation for
`customRelationship`, ensuring the field is an object before accessing
its properties.

This prevents the export from failing and makes custom field transforms
more resilient to missing or optional values.
2025-06-25 11:45:09 -04:00
Patrik
6d768748a0 fix(plugin-import-export): csv export for polymorphic relationship fields (#12926)
### What?

Fixes CSV export support for polymorphic relationship and upload fields.

### Why?

Polymorphic fields in Payload use a `{ relationTo, value }` structure.
The previous implementation incorrectly accessed `.id` directly on the
top-level object, which caused issues depending on query depth or data
shape. This led to missing or invalid values in exported CSVs.

### How?

- Updated getCustomFieldFunctions to safely access relationTo and
value.id from polymorphic fields

- Ensured `hasMany` polymorphic fields export each related ID and
relationTo as separate CSV columns
2025-06-25 11:44:31 -04:00
Jessica Rynkar
1845669e68 fix(ui): updates auth fields UI to reflect access control (#12745)
### What?
Reflects any access control restrictions applied to Auth fields in the
UI. I.e. if `email` has `update: () => false` the field should be
displayed as read-only.

### Why?
Currently any access control that is applied to auth fields is
functional but is not matched within the UI.

For example:
- `password` that does not have read access will not return data, but
the field will still be shown when it should be hidden
- `email` that does not have update access, updating the field and
saving the doc will **not** update the data, but it should be displayed
as read-only so nothing can be filled out and the updating restriction
is made clear

### How?
Passes field permissions through to the Auth fields UI and adds docs
with instructions on how to override auth field access.

#### Testing
Use `access-control` test suite and `auth` collection. Tests added to
`access-control` e2e.

Fixes #11569
2025-06-25 14:55:07 +01:00
Jarrod Flesch
0d50799b79 fix(ui): folder server function must reference exports dir (#12898) 2025-06-25 09:52:39 -04:00
Jessica Rynkar
37c945b95b fix(ui): custom row labels on arrays should not be removed on field duplication (#12895)
### What?
This fix prevents custom row labels being removed when duplicating array
items.

### Why?
Currently, when you have an array with custom row labels, if you create
a new array item by duplicating an existing item, the new item will have
no custom row label until you refresh the page.

### How?
During the `duplicate` process, we remove any react components from the
field state. This change intentionally re-adds the `RowLabel` if one
exists.

#### Reported by client
2025-06-25 09:44:00 -04:00
Jacob Fletcher
20bbbcfca2 fix(ui): date format of useAsTitle lost after changing value (#12928)
When a collection's `admin.useAsTitle` property points to a date field,
the date format is lost after making a change to the field's value.

Before:


https://github.com/user-attachments/assets/10e61517-3245-4645-be4c-33017bfc860c

After:


https://github.com/user-attachments/assets/d3d62d2e-364e-48a2-91c1-2ce4b0962fe5

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210632330039313
2025-06-25 09:15:55 -04:00
Sasha
cf87871fbd test: fix database/int.spec.ts with postgres custom schema (#12922)
The test was failing because in case you have a custom schema, you need
to use `payload.db.pgSchema.table` instead of `pgTable` to define a
table
2025-06-24 15:07:17 -04:00
Patrik
751691aeaf fix(plugin-import-export): omit CSV columns when toCSV returns undefined (#12923)
### What?

Ensure fields using a custom `toCSV` function that return `undefined`
are excluded from the exported CSV.

### Why?

Previously, when a `toCSV` function returned `undefined`, the field key
would still be added to the export row. This caused the column to appear
in the CSV output with an empty string value (`""`), leading to
unexpected results and failed assertions in tests expecting the field to
be truly omitted.

### How?

Updated the `flattenObject` utility to:
- Check if the value returned by a `toCSV` function is `undefined`
- Only assign the value to the export row if it is explicitly defined
- Applied this logic in all relevant paths (arrays, objects, primitives)

This change ensures that fields are only included in the CSV when a
meaningful value is returned.
2025-06-24 11:34:58 -07:00
Anatoly Kopyl
c03e9c1724 fix(ui): properly differentiate between DOM events and raw values in setValue (#12892)
Because of this check, if a JSON with a property `target` was saved it
would become malformed.

For example trying to save a JSON field:

```json
{
  "target": {
    "value": {
      "foo": "bar"
    }
  }
}
```

would result in:

```json
{
  "foo": "bar"
}
```

And trying to save:

```json
{
  "target": "foo"
}
```

would just not save anything:

```json
null
```

I went through all of the field types and did not find a single one that
would rely on this ternary. Seems like it always defaulted to `const val
= e`, except the unexpected case described previously.

Fixes #12873

Added test may be overkill, will remove if so.




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

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-24 14:30:52 -04:00
Sasha
b74969d720 fix(db-postgres): querying on hasMany: true select field in a relationship (#12916)
Fixes https://github.com/payloadcms/payload/issues/11635
2025-06-24 21:25:48 +03:00
Said Akhrarov
39e95195e1 fix(next): prevent errors in globals version view (#12920)
### What?
This PR fixes a runtime error that occurs when opening the "More
versions..." drawer while browsing the versions for a global. It also
fixes a minor runtime error when navigating to a global version view
where an optional chaining operator was missing as the collection
variable would be undefined as we are viewing a global.

This PR also adds an e2e test to ensure the versions drawer is
accessible and renders the appropriate number of versions for globals.

### Why?
To properly render global version views without errors.

### How?
By threading the global slug to the versions drawer and adjusting some
properties of the `renderDocument` server function call there. This PR
also adds an optional chaining operator the `versionUseAsTitle` in the
original view to prevent an error in globals.

Notes:
- This was brought to my attention in Discord by a handful of users

Before: (Missing optional chaining error)


[error1-verions-Editing---Menu---Payload.webm](https://github.com/user-attachments/assets/3dc4dbe4-ee5a-43df-8d25-05128b05e063)

Before: (Versions drawer error)


[error2-versions-Editing---Menu---Payload.webm](https://github.com/user-attachments/assets/98c3e1da-cb0b-4a36-bafd-240f641e8814)


After:


[versions-globals-Dashboard---Payload.webm](https://github.com/user-attachments/assets/c778d3f0-a8fe-4e31-92cb-62da8e6d8cb4)
2025-06-24 13:18:25 -04:00
Sasha
886c07e918 test: fix database integration tests with postgres (#12919)
Fixes failing postgres integration tests in the `database` test suite
2025-06-24 10:59:47 -04:00
Alessio Gravili
053192c488 refactor: changed default exports to named exports in payload package (#12871)
This changes all remaining default exports to named exports in the
payload package and removes all unnecessary internal-only barrel export
files. => Less lines of code, less eslint warnings

![Screenshot 2025-06-19 at 14 02
23@2x](https://github.com/user-attachments/assets/bcbe2394-07b5-49b4-86c7-30243679bb61)
2025-06-24 04:38:02 +00:00
Sasha
bc9b501e28 fix: querying virtual fields deeply with draft: true (#12868)
Fixes an issue when querying deeply new relationship virtual fields with
`draft: true`. Changes the method for `where` sanitization, before it
was done in `validateSearchParam` which didn't work with versions
properly, now there's a separate `sanitizeWhereQuery` function that does
this.
2025-06-23 22:18:49 -04:00
Alessio Gravili
bb17cc3ea8 refactor: remove unused assets, move remaining assets out of payload packages (#12874)
This PR removes the `packages/payload/src/assets` folder for the
following reasons:
- they were published to npm. Removing this decreases the install size
of payload (excluding dependencies) from 6.22MB => 5.12MB
- most assets were unused. The only used ones were moved to a different
directory that does not get published to npm

This also updates some outdated asset URLs in our examples
2025-06-23 23:23:44 +00:00
Jacob Fletcher
1b5e3fe8ba fix(next): remove error handling from next auth functions (#12897)
The `@payloadcms/next/auth` functions are unnecessarily wrapped with
`try...catch` blocks that propagate the original error as a plain
string. This makes it impossible for the end user's error handling to
differentiate between error types.

These functions also throw errors regardless, and therefore must be
wrapped with proper error handling anyway. Especially after removing the
internal logging in #12881, these blocks do not serve any purpose.

This PR also removes unused imports.
2025-06-23 16:16:37 -04:00
Elliot DeNolf
ca0d0360e0 ci: revert bump pnpm to v10 (#12840) (#12906)
The bump to pnpm v10 was causing too many mysterious timeouts in a few
places. Reverting until we can fully investigate.
2025-06-23 15:10:51 -04:00
Chandler Gonzales
fe58f03189 fix(next): remove console.error from next auth functions (#12881)
### What?

Removes the console.error() statement when there is a login error.

### Why?

IMO, Libraries should not pollute the console with log statements in all
but the most exceptional cases. This prevents users of the library from
controlling what goes to standard out. For example, if I want to use
structured logging, this log line breaks it.

It would be a little better if this console.error() only executed on
unexpected errors, but it executes even when a user puts the wrong email
/ password, so it gets printed relatively frequently.

I think you can just remove the logging and let the user of this
function catch the error and log as they see fit.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-23 15:01:29 -04:00
Elliot DeNolf
c7dc1b46c2 ci: add timeout-minutes for int and e2e (#12903)
Setting `timeout-minutes` to `45` for all int and e2e tests.
2025-06-23 13:56:16 -04:00
Jarrod Flesch
a44e4c46c5 ci: adjust neverBuiltDependencies in test/package.json (#12896)
Fixes an issue introduced with
4831f66f63
that prevents CI from running the built code

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-06-23 12:26:59 -04:00
Andrea Ghidini
57f4fb6cfe chore: fix withPayload helper jsdoc (#12503)
<!--

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?
I found that the `devBundleServerPackages` parameter is not present in
the documentation because it was spelled as `sortOnOptions`.

### Why?
Cannot find `devBundleServerPackages` using vscode intellisense. 

### How?
I simply changed back `sortOnOptions` to `options` in JSDoc comments.
2025-06-22 22:52:18 -04:00
Anatoly Kopyl
fcaf9893bd docs: filterOptions anchor link fix (#12883)
<!--

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 anchor links leading to
[`filterOptions`](https://payloadcms.com/docs/fields/select#filteroptions)

### How?

Replaced camel case with lower case.
2025-06-22 22:49:12 -04:00
Adler Weber
dede3a4759 docs(plugin-sentry): add pg query instrumentation guide (#12229)
<!--

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?

Hi Payload Team, this PR is a reply to @DanRibbens's request to document
#11478. Let me know if you'd like to see any changes - thank you!
2025-06-22 22:45:27 -04:00
Marcus Michaels
7a0308fb9b docs: fix typo on authentication overview page (#12891)
This is teeny tiny – the sentence "Out of the box Payload ships with a
three powerful Authentication strategies:" has an unnecessary "a" on the
Authentication overview page. This PR removes it.
2025-06-22 21:56:38 +00:00
Philip
6c4dfe45e6 fix: use small pill size when viewing version information (#12844)
![2025-06-17_18-59](https://github.com/user-attachments/assets/9b8d7e73-2d49-42a5-a504-34e6efd81283)

![2025-06-17_18-59_1](https://github.com/user-attachments/assets/732a44ff-5af1-4536-bf7b-fe1dc91d65ed)

This fixes bug listed here --
https://github.com/payloadcms/payload/issues/12839

I have not touched the general styling, but in my opinion this component
could benefit from using a margin.

---------

Co-authored-by: Philip <stuckinsnow@users.noreply.github.com>
Co-authored-by: Jessica Rynkar <67977755+jessrynkar@users.noreply.github.com>
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
2025-06-19 11:20:09 +00:00
Sasha
a5ec55c02a feat: collection-level disableBulkEdit (#12850) 2025-06-19 09:18:29 +00:00
Alessio Gravili
11ac230905 fix(richtext-lexical): consistent html converter inline padding (#12848)
Fixes https://github.com/payloadcms/payload/issues/12847

- Uses rem instead of em for inline padding, for indent consistency
between nodes with different font sizes
- Use rem instead of px in deprecated html converters for consistency

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210564720112211
2025-06-18 21:43:42 -07:00
Elliot DeNolf
4831f66f63 chore: remove neverBuiltDependencies from test/package.json 2025-06-18 13:05:54 -04:00
Elliot DeNolf
85e0e0ea1e ci: bump pnpm to v10 (#12840)
Bump pnpm to v10
2025-06-18 13:04:41 -04:00
Jessica Rynkar
25e3902242 fix(ui): should select document after creation from relationship field (#12842)
### What?
After creating a new document from a relationship field, this doc should
automatically become the selected document for that relationship field.
This is the expected and current behavior. However, when the
relationship ties to a collection with autosave enabled, this does not
happen.

### Why?
This is expected behavior and should still happen when the relationship
is using an autosave enabled collection.

### How?
1. The logic in `addNewRelation` contained an `if` statement that
checked for `operation === 'create'` - however when autosave is enabled,
the `create` operation runs on the first data update and subsequently it
is a `update` operation.
2. The `onSave` from the document drawer provider was not being run as
part of the autosave workflow.

#### Reported by client.
2025-06-18 11:42:36 +01:00
Alessio Gravili
59f536c2c9 refactor: simplify job queue error handling (#12845)
This simplifies workflow / task error handling, as well as cancelling
jobs. Previously, we were handling errors when they occur and passing
through error state using a `state` object - errors were then handled in
multiple areas of the code.

This PR adds new, clean `TaskError`, `WorkflowError` and
`JobCancelledError` errors that are thrown when they occur and are
handled **in one single place**, massively cleaning up complex functions
like
[payload/src/queues/operations/runJobs/runJob/getRunTaskFunction.ts](https://github.com/payloadcms/payload/compare/refactor/jobs-errors?expand=1#diff-53dc7ccb7c8e023c9ba63fdd2e78c32ad0be606a2c64a3512abad87893f5fd21)

Performance will also be positively improved by this change -
previously, as task / workflow failure or cancellation would have
resulted in multiple, separate `updateJob` db calls, as data
modifications to the job object required for storing failure state were
done multiple times in multiple areas of the codebase. Most notably,
task error state was handled and updated separately from workflow error
state.
Now, it's just a clean, single `updateJob` call

This PR also does the following:
- adds a new test for `deleteJobOnComplete` behavior
- cleans up test suite
- ensures `deleteJobOnComplete` does not delete definitively failed jobs

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210553277813320
2025-06-17 22:24:53 +00:00
Patrik
dffdee89d8 fix(ui): support react node content in ConfirmationModal heading and body (#12841)
### What?

This update improves the flexibility of the ConfirmationModal by
allowing the `heading` and `body` prop to accept either a string or a
React node.

If the `heading` or `body` is a string, it will be wrapped in its
respective tags for consistent styling.

If it's already a React element, it will be rendered as-is. This
prevents layout issues when passing JSX content like lists, links, or
formatted elements into the modal heading and body.
2025-06-17 11:19:55 -07:00
Paul
9c5adba5c6 chore: add eslint rule to ignore default exports in test suite configs (#12655)
Adds eslint rule `no-restricted-exports` with value `off` for payload
config files inside our `test` suite since we have to export with
default from those
2025-06-17 09:10:42 -04:00
Elliot DeNolf
d1826c647f templates: bump for v3.43.0 (#12831)
🤖 Automated bump of templates for v3.43.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-17 09:05:15 -04:00
Alessio Gravili
84cb2b5819 refactor: simplify job type (#12816)
Previously, there were multiple ways to type a running job:
- `GeneratedTypes['payload-jobs']` - only works in an installed project
- is `any` in monorepo
- `BaseJob` - works everywhere, but does not incorporate generated types
which may include type for custom fields added to the jobs collection
- `RunningJob<>` - more accurate version of `BaseJob`, but same problem

This PR deprecated all those types in favor of a new `Job` type.
Benefits:
- Works in both monorepo and installed projects. If no generated types
exist, it will automatically fall back to `BaseJob`
- Comes with an optional generic that can be used to narrow down
`job.input` based on the task / workflow slug. No need to use a separate
type helper like `RunningJob<>`

With this new type, I was able to replace every usage of
`GeneratedTypes['payload-jobs']`, `BaseJob` and `RunningJob<>` with the
simple `Job` type.

Additionally, this PR simplifies some of the logic used to run jobs
2025-06-16 16:15:56 -04:00
Elliot DeNolf
810869f3fa chore(release): v3.43.0 [skip ci] 2025-06-16 16:09:14 -04:00
Sasha
215f49efa5 feat(db-postgres): allow to store blocks in a JSON column (#12750)
Continuation of https://github.com/payloadcms/payload/pull/6245.
This PR allows you to pass `blocksAsJSON: true` to SQL adapters and the
adapter instead of aligning with the SQL preferred relation approach for
blocks will just use a simple JSON column, which can improve performance
with a large amount of blocks.

To try these changes you can install `3.43.0-internal.c5bbc84`.
2025-06-16 16:03:35 -04:00
Sasha
704518248c fix(db-postgres): reordering of enum values, bump drizzle-kit@0.31.0 and drizzle-orm@0.43.1 (#12256)
Fixes the issue when reordering select field options in postgres by
bumping `drizzle-kit` and `drizzle-orm`, related PR
https://github.com/drizzle-team/drizzle-orm/pull/4330
```
cannot drop type enum_users_select because other objects depend on it
```

fixes https://github.com/payloadcms/payload/discussions/8544
2025-06-16 16:03:18 -04:00
Jessica Rynkar
b372a34ebf feat(ui): adds constructorOptions to upload config (#12766)
### What?
Adds `constructorOptions` property to the upload config to allow any of
[these options](https://sharp.pixelplumbing.com/api-constructor/) to be
passed to the Sharp library.

### Why?
Users should be able to extend the Sharp library config as needed, to
define useful properties like `limitInputPixels` etc.

### How?
Creates new config option `constructorOptions` which passes any
compatible options directly to the Sharp library.

#### Reported by client.
2025-06-16 14:56:05 -04:00
Alessio Gravili
769ca03bff fix: ensure job autoruns are not triggered if jobs collection not enabled (#12808)
Fixes https://github.com/payloadcms/payload/issues/12776

- Adds a new `jobs.config.enabled` property to the sanitized config,
which can be used to easily check if the jobs system is enabled (i.e.,
if the payload-jobs collection was added during sanitization). This is
then checked before Payload sets up job autoruns.
- Fixes some type issues that occurred due to still deep-requiring the
jobs config - we forgot to omit it from the `DeepRequired(Config)` call.
The deep-required jobs config was then incorrectly merged with the
sanitized jobs config, resulting in a SanitizedConfig where all jobs
config properties were marked as required, even though they may be
undefined.
2025-06-16 14:53:23 -04:00
Alessio Gravili
4e2e4d2aed feat(next): version view overhaul (#12027)
#11769 improved the lexical version view diff component. This PR
improves the rest of the version view.

## What changed

- Column layout when selecting a version:
	- Previously: Selected version on the left, latest version on the left
- Now: Previous version on the left, previous version on the right
(mimics behavior of GitHub)
- Locale selector now displayed in pill selector, rather than
react-select
- Smoother, more reliable locale, modifiedOnly and version selection.
Now uses clean event callbacks rather than useEffects
- React-diff-viewer-continued has been replaced with the html differ we
use in lexical
- Updated Design for all field diffs
- Version columns now have a clearly defined separator line
- Fixed collapsibles showing in version view despite having no modified
fields if modifiedOnly is true
- New, redesigned header
	

## Screenshots

### Before

![CleanShot 2025-04-11 at 20 10
03@2x](https://github.com/user-attachments/assets/a93a500a-3cdd-4cf0-84dd-cf5481aac2b3)

![CleanShot 2025-04-11 at 20 10
28@2x](https://github.com/user-attachments/assets/59bc5885-cbaf-49ea-8d1d-8d145463fd80)

### After

![Screenshot 2025-06-09 at 17 43
49@2x](https://github.com/user-attachments/assets/f6ff0369-76c9-4c1c-9aa7-cbd88806ddc1)

![Screenshot 2025-06-09 at 17 44
50@2x](https://github.com/user-attachments/assets/db93a3db-48d6-4e5d-b080-86a34fff5d22)

![Screenshot 2025-06-09 at 17 45
19@2x](https://github.com/user-attachments/assets/27b6c720-05fe-4957-85af-1305d6b65cfd)

![Screenshot 2025-06-09 at 17 45
34@2x](https://github.com/user-attachments/assets/6d42f458-515a-4611-b27a-f4d6bafbf555)
2025-06-16 07:58:03 -04:00
Sasha
9943b3508d fix: filtering joins in where by ID (#12804)
Fixes https://github.com/payloadcms/payload/issues/12768

Example:
```
const found_1 = await payload.find({
  collection: 'categories',
  where: { 'relatedPosts.id': { equals: post.id } },
})
```
or
```
const found_2 = await payload.find({
  collection: 'categories',
  where: { relatedPosts: { equals: post.id } },
})
```
2025-06-13 14:13:17 -04:00
Dan Ribbens
8235fe137f fix(plugin-import-export): download button in collection edit view (#12805)
The custom save button showing on export enabled collections in the edit
view. Instead it was meant to only appear in the export collection. This
makes it so it only appears in the export drawer.
2025-06-13 12:51:13 -04:00
Kendell Joseph
f2e04222f4 feat: admin upload controls (#11615)
### What?
Adds the ability to add additional components to the file upload
component.

```ts
export const Media: CollectionConfig = {
  slug: 'media',
  upload: {
    admin: {
      components: {
        controls: [
          '/collections/components/Control/index.js#UploadControl',
        ],
      },
    },
  },
  fields: [],
}
```

![image](https://github.com/user-attachments/assets/4706e05b-4e95-4f15-8444-a279c589074e)

### Provider
Use the `useUploadControls` provider to either `setUploadControlFile`
passing a file object, or set the file by url using
`setUploadControlFileUrl`.

```tsx
'use client'
import { Button, useUploadControls } from '@payloadcms/ui'
import React, { useCallback } from 'react'

export const UploadControl = () => {
  const { setUploadControlFile, setUploadControlFileUrl } = useUploadControls()

  const loadFromFile = useCallback(async () => {
    const response = await fetch('https://payloadcms.com/images/universal-truth.jpg')
    const blob = await response.blob()
    const file = new File([blob], 'universal-truth.jpg', { type: 'image/jpeg' })
    setUploadControlFile(file)
  }, [setUploadControlFile])

  const loadFromUrl = useCallback(() => {
    setUploadControlFileUrl('https://payloadcms.com/images/universal-truth.jpg')
  }, [setUploadControlFileUrl])

  return (
    <div>
      <Button id="load-from-file-upload-button" onClick={loadFromFile}>
        Load from File
      </Button>
      <br />
      <Button id="load-from-url-upload-button" onClick={loadFromUrl}>
        Load from URL
      </Button>
    </div>
  )
}
```


### Why?
Add the ability to use a custom component to select a document to
upload.
2025-06-13 12:47:46 -04:00
Dan Ribbens
3edcc40174 fix(plugin-import-export): incorrect custom type on toCSVFunction changed to toCSV (#12796)
Type declaration extending `custom.['plugin-import-export']` was
incorrectly named `toCSVFunction` instead of `toCSV`. This changes the
type to match the correct property name `toCSV`.
2025-06-13 12:35:06 -04:00
Sasha
e60db0750a fix(ui): reordering with a join field inside a group (#12803)
Fixes https://github.com/payloadcms/payload/issues/12802
2025-06-13 12:31:07 -04:00
Alessio Gravili
06ad17108b fix: change payload.jobs.run and bin script to only run jobs from default queue by default, adds support for allQueues argument (#12799)
By default, calling `payload.jobs.run()` will incorrectly run all jobs
from all queues. The `npx payload jobs:run` bin script behaves the same
way.

The `payload-jobs/run` endpoint runs jobs from the `default` queue,
which is the correct behavior.

This PR does the following:
- Change `payload.jobs.run()` to only runs jobs from the `default` queue
by default
- Change `npx payload jobs:run` bin script to only runs jobs from the
`default` queue by default
- Add new allQueues / --all-queues arguments/queryparams/flags for the
local API, rest API and bin script to allow you to run all jobs from all
queues
- Clarify the docs
2025-06-13 09:10:02 -07:00
Jessica Rynkar
65309b1d21 feat(next): reorder document view tabs (#12288)
Introduces the ability to customize the order of both default and custom
tabs. This way you can make custom tabs appear before default ones, or
change the order of tabs as you see fit.

To do this, use the new `tab.order` property in your edit view's config:

```ts
import type { CollectionConfig } from 'payload'

export const MyCollectionConfig: CollectionConfig = {
  // ...
  admin: {
    components: {
      views: {
        edit: {
          myCustomView: {
            path: '/my-custom-view',
            Component: '/path/to/component',
            tab: {
              href: '/my-custom-view',
              order: 100, // This will put this tab in the first position
            },
          }
        }
      }
    }
  }
}
```

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-13 15:28:29 +01:00
Sasha
245a2dee7e fix(db-mongodb): 4x and more level deep relationships querying (#12800)
Fixes https://github.com/payloadcms/payload/issues/12721
2025-06-13 14:11:13 +00:00
Paul
7fef589ffa docs: fix header custom component example (#12801)
Minor fix from `Header` to `header`
2025-06-13 06:59:03 -07:00
Jacob Fletcher
53835f2620 refactor(next): simplify document tab rendering logic (#12795)
Simplifies the rendering logic around document tabs in the following
ways:

- Merges default tabs with custom tabs in a more predictable way, now
there is only a single array of tabs to iterate over, no more concept of
"custom" tabs vs "default" tabs, there's now just "tabs"
- Deduplicates rendering conditions for all tabs, now any changes to
default tabs would also apply to custom tabs with half the code
- Removes unnecessary `getCustomViews` function, this is a relic of the
past
- Removes unnecessary `getViewConfig` function, this is a relic of the
past
- Removes unused `references`, `relationships`, and `version` key
placeholders, these are relics of the past
- Prevents tab conditions from running twice unnecessarily
- Other misc. cleanup like unnecessarily casting the tab conditions
result to a boolean, etc.
2025-06-13 07:12:28 -04:00
Jayce Pulsipher
729b676e98 fix(plugin-nested-docs): check error name that is changed at compile time (#12798)
Since `ValidationErrorName` [gets dynamically reassigned during
compilation](https://github.com/payloadcms/payload/blob/main/packages/payload/src/errors/ValidationError.ts#L11),
we must reference the variable rather than use a static string to
compare against

Co-authored-by: Jayce Pulsipher <jpulsipher@nav.com>
2025-06-13 03:39:15 -07:00
Patrik
77f380544f fix(ui): adjust alignment of list header actions (#12793)
### Before
![Screenshot 2025-06-12 at 11 54
58 AM](https://github.com/user-attachments/assets/82ecfe5a-b483-43da-abb7-1a2b1b548807)

### After
![Screenshot 2025-06-12 at 11 55
12 AM](https://github.com/user-attachments/assets/7b017ec3-f31a-4985-905f-951cface0c5c)


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210535479281746
2025-06-12 10:03:06 -07:00
Sasha
df8be92d47 fix(db-postgres): x3 and more nested blocks regression (#12770) 2025-06-12 19:37:07 +03:00
Jarrod Flesch
e7b5884ec2 fix(ui): not showing hyphenated field values in table (#12791) 2025-06-12 12:36:24 -04:00
Patrick Roelofs
d0e647a992 fix(plugin-redirects): add missing optional chaining (#12753)
### What?
Updates the redirects plugin types to make collections a required type

### Why?
Currently not including the collections object when importing the plugin
causes an error to occur when going to the page in the UI, also it
cannot generate types. Likely due to it unable to make a reference to a
collection.

### How?
Makes collections required

Fixes #12709

---------

Co-authored-by: Patrick Roelofs <patrick.roelofs@iquality.nl>
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-06-12 16:29:49 +00:00
Jessica Rynkar
9364d51f4b fix(ui): inconsistent pill sizes across admin panel (#12788)
### What?
Fixes inconsistent `pill` sizes across the Admin Panel.

### How?
Pills without a specified size default to **medium**. In the folders
[PR](https://github.com/payloadcms/payload/pull/10030), additional
padding was to the medium size. As a result, any pills without an
explicit size now appear larger than intended.

This PR fixes that by updating any pills that should be small to
explicitly set `size="small"`.

Fixes #12752
2025-06-12 15:43:00 +01:00
Jacob Fletcher
b556fe3daf refactor(next): simplify document view routing (#12777)
Simplifies document routing in the following ways:

- Removes duplicative code blocks that made it easy to make changes to
collections but not globals
- Consolidates `CustomView`, `DefaultView`, and `ErrorView` into just
`View`
- Removes unnecessary `overrideDocPermissions` arg
- Standardizes the 404 logic when the doc lacks read access
- Fixes styling issue where `UnauthorizedView` is rendered without
margins, e.g. when you lack permission to read versions but navigate to
`/versions`
2025-06-12 10:01:22 -04:00
Kendell Joseph
c04c257712 chore: adds filters (#12622)
Filters URLs to avoid issues with SSRF

Had to use `undici` instead of native `fetch` because it was the only
viable alternative that supported both overriding agent/dispatch and
also implemented `credentials: include`.

[More info
here.](https://blog.doyensec.com/2023/03/16/ssrf-remediation-bypass.html)

---------

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
2025-06-12 09:46:49 -04:00
Jacob Fletcher
f64a0aec5f fix: remove unsupported path property from default document view configs (#12774)
Customizing the `path` property on default document views is currently
not supported, but the types suggest that it is. You can only provide a
path to custom views. This PR ensures that `path` cannot be set on
default views as expected.

For example:

```ts
import type { CollectionConfig } from 'payload'

export const MyCollectionConfig: CollectionConfig = {
  // ...
  admin: {
    components: {
      views: {
        edit: {
          default: {
            path: '/' // THIS IS NOT ALLOWED!
          },
          myCustomView: {
            path: '/edit', // THIS IS ALLOWED!
            Component: '/collections/CustomViews3/MyEditView.js#MyEditView',
          },
        },
      },
    },
  },
}
```

For background context, this was deeply explored in #12701. This is not
planned, however, due to [performance and maintainability
concerns](https://github.com/payloadcms/payload/pull/12701#issuecomment-2963926925),
plus [there are alternatives to achieve
this](https://github.com/payloadcms/payload/pull/12772).

This PR also fixes and improves various jsdocs, and fixes a typo found
in the docs.
2025-06-12 09:01:20 -04:00
Paul
143aff57ae fix: field inside an unnamed group field erroring when used as a title (#12771)
Fixes https://github.com/payloadcms/payload/issues/12632

Config sanitisation will error without this PR when attempting to
useAsTitle a field inside an unnamed group field.
2025-06-12 05:57:37 -07:00
Alessio Gravili
cf43c5cd08 fix: error when saving global with versioning enabled (#12778)
When saving a global with versioning enabled as draft, and then
publishing it, the following error may appear: `[16:50:35] ERROR: Could
not find createdAt or updatedAt in latestVersion.version`

This is due to an incorrect check to appease typescript strict mode. We
shouldn't throw if `version.updatedAt` doesn't exist - the purpose of
this logic is to add that property if it doesn't exist
2025-06-12 01:39:13 +00:00
Alessio Gravili
67fb29b2a4 fix: reduce global DOM/Node type conflicts in server-only packages (#12737)
Currently, we globally enable both DOM and Node.js types. While this
mostly works, it can cause conflicts - particularly with `fetch`. For
example, TypeScript may incorrectly allow browser-only properties (like
`cache`) and reject valid Node.js ones like `dispatcher`.

This PR disables DOM types for server-only packages like payload,
ensuring Node-specific typings are applied. This caught a few instances
of incorrect fetch usage that were previously masked by overlapping DOM
types.

This is not a perfect solution - packages that contain both server and
client code (like richtext-lexical or next) will still suffer from this
issue. However, it's an improvement in cases where we can cleanly
separate server and client types, like for the `payload` package which
is server-only.

## Use-case

This change enables https://github.com/payloadcms/payload/pull/12622 to
explore using node-native fetch + `dispatcher`, instead of `node-fetch`
+ `agent`.

Currently, it will incorrectly report that `dispatcher` is not a valid
property for node-native fetch
2025-06-11 20:59:19 +00:00
Anders Semb Hermansen
018317dfba fix(storage-azure): return error status 404 when file is not found instead of 500 (#11734)
### What?

The azure storage adapter returns a 500 internal server error when a
file is not found.
It's expected that it will return 404 when a file is not found.

### Why?

There is no checking if the blockBlobClient exists before it's used, so
it throws a RestError when used and the blob does not exist.

### How?

Check if exception thrown is of type RestError and have a 404 error from
the Azure API and return a 404 in that case.

An alternative way would be to call the exists() method on the
blockBlobClient, but that will be one more API call for blobs that does
exist. So I chose to check the exception instead.

Also added integration tests for azure storage in the same manner as s3,
as it was missing for azure storage.
2025-06-11 07:49:34 -07:00
Dan Ribbens
37afbe6c04 fix: orderable has incorrect sort results depending on capitalization (#12758)
### What?
The results when querying orderable collections can be incorrect due to
how the underlying database handles sorting when capitalized letters are
introduced.

### Why?
The original fractional indexing logic uses base 62 characters to
maximize the amount of data per character. This optimization saves a few
characters of text in the database but fails to return accurate results
when mixing uppercase and lowercase characters.

### How?
Instead we can use base 36 values instead (0-9,a-z) so that all
databases handle the sort consistently without needing to introduce
collation or other alternate solutions.

Fixes #12397
2025-06-11 09:49:53 -04:00
Patrik
458a04b77c feat: expose data argument in afterChange hook for collections and globals (#12756)
### What

This PR updates the `afterChange` hook for collections and globals to
include the `data` argument.

While the `doc` argument provides the saved version of the document,
having access to the original `data` allows for additional context—such
as detecting omitted fields, raw client input, or conditional logic
based on user-supplied data.

### Changes

- Adds the `data` argument to the `afterChange` hook args.
- Applies to both `collection` and `global` hooks.

### Example

```
afterChange: [
  ({ context, data, doc, operation, previousDoc, req }) => {
    if (data?.customFlag) {
       // Perform logic based on raw input
    }
  },
],
```
2025-06-11 06:23:22 -07:00
Said Akhrarov
d8626adc3b fix(storage-gcs): return 404 on file not found instead of 500 (#11746)
<!--

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?
In a similar vein to #11734, #11733, #10327 - this PR returns a 404 in
the response when a file is not found while using the `storage-gcs`
adapter. Currently a 500 is returned.

### Why?
To return the correct error level in the response when a file is not
found when using `storage-gcs`.

### How?
The GCS nodejs library exposes the `ApiError` as a general error - these
changes check that the caught error is an instance of this class and if
the provided code is a `404`.
2025-06-11 06:15:53 -07:00
Anders Semb Hermansen
a19921d08f fix(storage-s3): return error status 404 when file is not found instead of 500 (#11733)
### What?

The s3 storage adapter returns a 500 internal server error when a file
is not found.
It's expected that it will return 404 when a file is not found.

### Why?

The getObject function from aws s3 sdk does not return undefined when a
blob is not found, but throws a NoSuchKey error:
https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-s3/Class/NoSuchKey/

### How?

Check if exception thrown is of type NoSuchKey and return a 404 in that
case.

Related discord discussion:

https://discord.com/channels/967097582721572934/1350826594062696539/1350826594062696539
2025-06-11 12:04:25 +00:00
Sasha
860e0b4ff9 fix(db-mongodb): bump mongoose to 8.15.1 (#12755)
Updates the `mongoose` package to the latest version `8.15.1`.
Fixes https://github.com/payloadcms/payload/issues/12708
2025-06-11 06:30:36 +03:00
Said Akhrarov
08d5b2b79d chore: remove invalid colon from workspaces key in package.json (#12757)
### What?
This PR removes an extra colon from the `"workspaces"` key which was
likely a typo.

### Why?
To use a properly recognized workspaces key without the extra colon.

### How?
Deletion of `:` from the workspaces key in `package.json`
2025-06-10 16:03:59 -07:00
Alessio Gravili
cb3f9bb3e9 perf(ui): do not re-animate drawer on re-render, reduce useEffects (#12743)
Previously, every time the drawer re-rendered a new entry animation may
be triggered. This PR fixes this by setting the open state to
`modalState[slug]?.isOpen` instead of `false`.

Additionally, I was able to simplify this component while maintaining
functionality. Got rid of one `useEffect` and one `useState` call. The
remaining useEffect also runs less often (previously, it ran every time
`modalState` changed => it re-ran if _any_ modal opened or closed, not
just the current one)
2025-06-10 17:14:58 -04:00
Elliot DeNolf
192cc97f6e templates: bump for v3.42.0 (#12732)
🤖 Automated bump of templates for v3.42.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-10 13:02:45 -07:00
Elliot DeNolf
3313ab7e75 chore(scripts): fix tsconfig.base.json reset 2025-06-10 15:14:51 -04:00
Dan Ribbens
c4e5831fbb fix(plugin-import-export): export all available fields by default (#12731)
When making an export of a collection, and no fields are selected then
you get am empty CSV. The intended behavior is that all data is exported
by default.

This fixes the issue that from the admin UI, when the fields selector
the resulting CSV has no columns.
2025-06-10 12:03:26 -04:00
Jarrod Flesch
a43d1a685f feat(ui): moves folder rendering from the client to the server (#12710) 2025-06-10 11:56:28 -04:00
Anyu Jiang
9d2817e647 fix: ensure redirect route is correctly formatted for "Copy to locale" (#12560) 2025-06-10 10:10:55 -04:00
Jarrod Flesch
254ffecaea fix(db-sqlite): sqlite unique validation messages (#12740)
Fixes https://github.com/payloadcms/payload/issues/12628

When using sqlite, the error from the db is a bit different than
Postgres.

This PR allows us to extract the fieldName when using sqlite for the
unique constraint error.
2025-06-10 10:08:06 -04:00
Sasha
38652d7b7c docs: remove outdated buildPath property (#12741)
Fixes https://github.com/payloadcms/payload/issues/12678
2025-06-10 08:57:19 -04:00
1654 changed files with 74662 additions and 20491 deletions

34
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,34 @@
# Order matters. The last matching pattern takes precedence
## Package Exports
**/exports/ @denolfe @DanRibbens
## Packages
/packages/create-payload-app/src/ @denolfe
/packages/email-*/src/ @denolfe
/packages/eslint-*/ @denolfe @AlessioGr
/packages/plugin-cloud-storage/src/ @denolfe
/packages/plugin-multi-tenant/src/ @JarrodMFlesch
/packages/richtext-*/src/ @AlessioGr
/packages/storage-*/src/ @denolfe
/packages/ui/src/ @jacobsfletch @AlessioGr @JarrodMFlesch
## Templates
/templates/_data/ @denolfe
/templates/_template/ @denolfe
## Build Files
**/jest.config.js @denolfe @AlessioGr
**/tsconfig*.json @denolfe @AlessioGr
## Root
/.github/ @denolfe
/.husky/ @denolfe
/.vscode/ @denolfe @AlessioGr
/package.json @denolfe
/tools/ @denolfe

View File

@@ -4,24 +4,17 @@ description: |
inputs:
node-version:
description: Node.js version
required: true
default: 23.11.0
description: Node.js version override
pnpm-version:
description: Pnpm version
required: true
default: 9.7.1
description: Pnpm version override
pnpm-run-install:
description: Whether to run pnpm install
required: false
default: true
pnpm-restore-cache:
description: Whether to restore cache
required: false
default: true
pnpm-install-cache-key:
description: The cache key for the pnpm install cache
default: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
description: The cache key override for the pnpm install cache
outputs:
pnpm-store-path:
@@ -37,15 +30,44 @@ runs:
shell: bash
run: sudo ethtool -K eth0 tx off rx off
- name: Get versions from .tool-versions or use overrides
shell: bash
run: |
# if node-version input is provided, use it; otherwise, read from .tool-versions
if [ "${{ inputs.node-version }}" ]; then
echo "Node version override provided: ${{ inputs.node-version }}"
echo "NODE_VERSION=${{ inputs.node-version }}" >> $GITHUB_ENV
elif [ -f .tool-versions ]; then
NODE_VERSION=$(grep '^nodejs ' .tool-versions | awk '{print $2}')
echo "NODE_VERSION=$NODE_VERSION" >> $GITHUB_ENV
echo "Node version resolved to: $NODE_VERSION"
else
echo "No .tool-versions file found and no node-version input provided. Invalid configuration."
exit 1
fi
# if pnpm-version input is provided, use it; otherwise, read from .tool-versions
if [ "${{ inputs.pnpm-version }}" ]; then
echo "Pnpm version override provided: ${{ inputs.pnpm-version }}"
echo "PNPM_VERSION=${{ inputs.pnpm-version }}" >> $GITHUB_ENV
elif [ -f .tool-versions ]; then
PNPM_VERSION=$(grep '^pnpm ' .tool-versions | awk '{print $2}')
echo "PNPM_VERSION=$PNPM_VERSION" >> $GITHUB_ENV
echo "Pnpm version resolved to: $PNPM_VERSION"
else
echo "No .tool-versions file found and no pnpm-version input provided. Invalid configuration."
exit 1
fi
- name: Setup Node@${{ inputs.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ inputs.pnpm-version }}
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Get pnpm store path
@@ -55,14 +77,25 @@ runs:
echo "STORE_PATH=$STORE_PATH" >> $GITHUB_ENV
echo "Pnpm store path resolved to: $STORE_PATH"
- name: Compute Cache Key
shell: bash
run: |
if [ -n "${{ inputs.pnpm-install-cache-key }}" ]; then
PNPM_INSTALL_CACHE_KEY="${{ inputs.pnpm-install-cache-key }}"
else
PNPM_INSTALL_CACHE_KEY="pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}"
fi
echo "Computed PNPM_INSTALL_CACHE_KEY: $PNPM_INSTALL_CACHE_KEY"
echo "PNPM_INSTALL_CACHE_KEY=$PNPM_INSTALL_CACHE_KEY" >> $GITHUB_ENV
- name: Restore pnpm install cache
if: ${{ inputs.pnpm-restore-cache == 'true' }}
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ inputs.pnpm-install-cache-key }}
key: ${{ env.PNPM_INSTALL_CACHE_KEY }}
restore-keys: |
pnpm-store-${{ inputs.pnpm-version }}-
pnpm-store-${{ env.PNPM_VERSION }}-
pnpm-store-
- name: Run pnpm install
@@ -72,5 +105,5 @@ runs:
# Set the cache key output
- run: |
echo "pnpm-install-cache-key=${{ inputs.pnpm-install-cache-key }}" >> $GITHUB_ENV
echo "pnpm-install-cache-key=${{ env.PNPM_INSTALL_CACHE_KEY }}" >> $GITHUB_OUTPUT
shell: bash

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

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -40,7 +40,7 @@ There are a couple ways run integration tests:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/int-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/int-debug.png" />
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
@@ -57,7 +57,7 @@ The easiest way to run E2E tests is to install
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/e2e-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/e2e-debug.png" />
#### Notes

36
.github/workflows/audit-dependencies.sh vendored Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
severity=${1:-"high"}
output_file="audit_output.json"
echo "Auditing for ${severity} vulnerabilities..."
audit_json=$(pnpm audit --prod --json)
echo "${audit_json}" | jq --arg severity "${severity}" '
.advisories | to_entries |
map(select(.value.patched_versions != "<0.0.0" and (.value.severity == $severity or ($severity == "high" and .value.severity == "critical"))) |
{
package: .value.module_name,
vulnerable: .value.vulnerable_versions,
fixed_in: .value.patched_versions,
findings: .value.findings
}
)
' >$output_file
audit_length=$(jq 'length' $output_file)
if [[ "${audit_length}" -gt "0" ]]; then
echo "Actionable vulnerabilities found in the following packages:"
jq -r '.[] | "\u001b[1m\(.package)\u001b[0m vulnerable in \u001b[31m\(.vulnerable)\u001b[0m fixed in \u001b[32m\(.fixed_in)\u001b[0m"' $output_file | while read -r line; do echo -e "$line"; done
echo ""
echo "Output written to ${output_file}"
cat $output_file
echo ""
echo "This script can be rerun with: './.github/workflows/audit-dependencies.sh $severity'"
exit 1
else
echo "No actionable vulnerabilities"
exit 0
fi

View File

@@ -0,0 +1,53 @@
name: audit-dependencies
on:
# Sundays at 2am EST
schedule:
- cron: '0 7 * * 0'
workflow_dispatch:
inputs:
audit-level:
description: The level of audit to run (low, moderate, high, critical)
required: false
default: high
debug:
description: Enable debug logging
required: false
default: false
env:
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
audit:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup
uses: ./.github/actions/setup
- name: Run audit dependencies script
id: audit_dependencies
run: ./.github/workflows/audit-dependencies.sh ${{ inputs.audit-level }}
- name: Slack notification on failure
if: failure()
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
payload: |
{
"username": "GitHub Actions Bot",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🚨 Actionable vulnerabilities found: <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Script Run Details>"
}
},
]
}

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

@@ -1,4 +1,4 @@
name: build
name: ci
on:
pull_request:
@@ -6,7 +6,6 @@ on:
- opened
- reopened
- synchronize
- labeled
push:
branches:
- main
@@ -17,8 +16,6 @@ concurrency:
cancel-in-progress: true
env:
NODE_VERSION: 23.11.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -36,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:
@@ -65,16 +62,12 @@ jobs:
lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Lint
run: pnpm lint -- --quiet
@@ -85,14 +78,10 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- run: pnpm run build:all
env:
@@ -109,16 +98,13 @@ 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
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -136,16 +122,13 @@ 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
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -163,17 +146,20 @@ jobs:
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
name: int-${{ matrix.database }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
database:
- mongodb
- firestore
- postgres
- postgres-custom-schema
- postgres-uuid
- supabase
- sqlite
- sqlite-uuid
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -185,7 +171,8 @@ jobs:
services:
postgres:
image: ${{ (startsWith(matrix.database, 'postgres') ) && 'postgis/postgis:16-3.4' || '' }}
# Custom postgres 17 docker image that supports both pg-vector and postgis: https://github.com/payloadcms/postgis-vector
image: ${{ (startsWith(matrix.database, 'postgres') ) && 'ghcr.io/payloadcms/postgis-vector:latest' || '' }}
env:
# must specify password for PG Docker container image, see: https://registry.hub.docker.com/_/postgres?tab=description&page=1&name=10
POSTGRES_USER: ${{ env.POSTGRES_USER }}
@@ -197,16 +184,13 @@ 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
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -258,6 +242,7 @@ jobs:
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
name: e2e-${{ matrix.suite }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
@@ -298,6 +283,8 @@ jobs:
- fields__collections__Text
- fields__collections__UI
- fields__collections__Upload
- group-by
- folders
- hooks
- lexical__collections__Lexical__e2e__main
- lexical__collections__Lexical__e2e__blocks
@@ -312,24 +299,23 @@ jobs:
- plugin-cloud-storage
- plugin-form-builder
- plugin-import-export
- plugin-multi-tenant
- plugin-nested-docs
- plugin-seo
- sort
- trash
- versions
- uploads
env:
SUITE_NAME: ${{ matrix.suite }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -384,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]
@@ -434,6 +421,8 @@ jobs:
- fields__collections__Text
- fields__collections__UI
- fields__collections__Upload
- group-by
- folders
- hooks
- lexical__collections__Lexical__e2e__main
- lexical__collections__Lexical__e2e__blocks
@@ -448,24 +437,23 @@ jobs:
- plugin-cloud-storage
- plugin-form-builder
- plugin-import-export
- plugin-multi-tenant
- plugin-nested-docs
- plugin-seo
- sort
- trash
- versions
- uploads
env:
SUITE_NAME: ${{ matrix.suite }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -520,24 +508,32 @@ jobs:
# report-tag: ${{ matrix.suite }}
# job-summary: true
# Build listed templates with packed local packages
build-templates:
# Build listed templates with packed local packages and then runs their int and e2e tests
build-and-test-templates:
runs-on: ubuntu-24.04
needs: build
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_build == 'true' }}
name: build-template-${{ matrix.template }}-${{ matrix.database }}
strategy:
fail-fast: false
matrix:
include:
- template: blank
database: mongodb
- template: website
database: mongodb
- template: with-payload-cloud
database: mongodb
- template: with-vercel-mongodb
database: mongodb
# Postgres
- template: with-postgres
database: postgres
- template: with-vercel-postgres
database: postgres
@@ -547,8 +543,6 @@ jobs:
# - template: with-vercel-website
# database: postgres
name: ${{ matrix.template }}-${{ matrix.database }}
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -556,16 +550,13 @@ jobs:
MONGODB_VERSION: 6.0
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -612,21 +603,57 @@ jobs:
env:
NODE_OPTIONS: --max-old-space-size=8096
- name: Store Playwright's Version
run: |
# Extract the version number using a more targeted regex pattern with awk
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --depth=0 | awk '/@playwright\/test/ {print $2}')
echo "Playwright's Version: $PLAYWRIGHT_VERSION"
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
- name: Cache Playwright Browsers for Playwright's Version
id: cache-playwright-browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
- name: Setup Playwright - Browsers and Dependencies
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Setup Playwright - Dependencies-only
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: Runs Template Int Tests
run: pnpm --filter ${{ matrix.template }} run test:int
env:
NODE_OPTIONS: --max-old-space-size=8096
PAYLOAD_DATABASE: ${{ matrix.database }}
POSTGRES_URL: ${{ env.POSTGRES_URL }}
MONGODB_URL: mongodb://localhost:27017/payloadtests
- name: Runs Template E2E Tests
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.template }}.json pnpm --filter ${{ matrix.template }} test:e2e
env:
NODE_OPTIONS: --max-old-space-size=8096
PAYLOAD_DATABASE: ${{ matrix.database }}
POSTGRES_URL: ${{ env.POSTGRES_URL }}
MONGODB_URL: mongodb://localhost:27017/payloadtests
NEXT_TELEMETRY_DISABLED: 1
tests-type-generation:
runs-on: ubuntu-24.04
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
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -647,7 +674,7 @@ jobs:
needs:
- lint
- build
- build-templates
- build-and-test-templates
- tests-unit
- tests-int
- tests-e2e
@@ -670,3 +697,36 @@ jobs:
- run: |
echo github.ref: ${{ github.ref }}
echo isV3: ${{ github.ref == 'refs/heads/main' }}
analyze:
runs-on: ubuntu-latest
needs: [changes, build]
timeout-minutes: 5
permissions:
contents: read # for checkout repository
actions: read # for fetching base branch bundle stats
pull-requests: write # for comments
steps:
- uses: actions/checkout@v5
- name: Node setup
uses: ./.github/actions/setup
with:
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
- name: Restore build
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- run: pnpm run build:bundle-for-analysis # Esbuild packages that haven't already been built in the build step for the purpose of analyzing bundle size
env:
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
- name: Analyze esbuild bundle size
# Temporarily disable this for community PRs until this can be implemented in a separate workflow
if: github.event.pull_request.head.repo.fork == false
uses: exoego/esbuild-bundle-analyzer@v1
with:
metafiles: 'packages/payload/meta_index.json,packages/payload/meta_shared.json,packages/ui/meta_client.json,packages/ui/meta_shared.json,packages/next/meta_index.json,packages/richtext-lexical/meta_client.json'

View File

@@ -7,8 +7,6 @@ on:
workflow_dispatch:
env:
NODE_VERSION: 23.11.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -19,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
@@ -56,13 +54,10 @@ jobs:
POSTGRES_DB: payloadtests
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Start PostgreSQL
uses: CasperWA/postgresql-action@v1.2
@@ -87,6 +82,11 @@ jobs:
with:
mongodb-version: 6.0
# The template generation script runs import map generation which needs the built payload bin scripts
- run: pnpm run build:all
env:
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
- name: Update template lockfiles and migrations
run: pnpm script:gen-templates

View File

@@ -12,17 +12,18 @@ on:
default: ''
env:
NODE_VERSION: 23.11.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
post_release:
permissions:
issues: write
pull-requests: write
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:
@@ -42,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

@@ -7,8 +7,6 @@ on:
workflow_dispatch:
env:
NODE_VERSION: 23.11.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -20,12 +18,9 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Load npm token
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:

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 }}

9
.gitignore vendored
View File

@@ -22,6 +22,13 @@ meta_server.json
meta_index.json
meta_shared.json
packages/payload/esbuild
packages/ui/esbuild
packages/next/esbuild
packages/richtext-lexical/esbuild
audit_output.json
.turbo
# Ignore test directory media folder/files
@@ -324,5 +331,7 @@ test/databaseAdapter.js
test/.localstack
test/google-cloud-storage
test/azurestoragedata/
/media-without-delete-access
licenses.csv

7
.vscode/launch.json vendored
View File

@@ -139,6 +139,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts trash",
"cwd": "${workspaceFolder}",
"name": "Run Dev Trash",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts uploads",
"cwd": "${workspaceFolder}",

View File

@@ -6,6 +6,8 @@
"source.fixAll.eslint": "explicit"
},
"editor.formatOnSaveMode": "file",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"eslint.rules.customizations": [
// Silence some warnings that will get auto-fixed
{ "rule": "perfectionist/*", "severity": "off", "fixable": true },

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

@@ -45,7 +45,7 @@ There are a couple ways to do this:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/int-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/int-debug.png" />
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
@@ -62,7 +62,7 @@ The easiest way to run E2E tests is to install
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/e2e-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/e2e-debug.png" />
#### Notes

View File

@@ -32,18 +32,18 @@ The Admin Panel serves as the entire HTTP layer for Payload, providing a full CR
Once you [install Payload](../getting-started/installation), the following files and directories will be created in your app:
```plaintext
app/
├─ (payload)/
├── admin/
├─── [[...segments]]/
app
├─ (payload)
├── admin
├─── [[...segments]]
├──── page.tsx
├──── not-found.tsx
├── api/
├─── [...slug]/
├── api
├─── [...slug]
├──── route.ts
├── graphql/
├── graphql
├──── route.ts
├── graphql-playground/
├── graphql-playground
├──── route.ts
├── custom.scss
├── layout.tsx
@@ -77,37 +77,38 @@ All auto-generated files will contain the following comments at the top of each
## Admin Options
All options for the Admin Panel are defined in your [Payload Config](../configuration/overview) under the `admin` property:
All root-level options for the Admin Panel are defined in your [Payload Config](../configuration/overview) under the `admin` property:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
// highlight-start
admin: {
// highlight-line
// ...
},
// highlight-end
})
```
The following options are available:
| Option | Description |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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). |
| **`buildPath`** | Specify an absolute path for where to store the built Admin bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
| **`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. |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`meta`** | Base metadata to use for the Admin Panel. [More details](./metadata). |
| **`routes`** | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| **`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) |
| **`user`** | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `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). |
| `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. |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `meta` | Base metadata to use for the Admin Panel. [More details](./metadata). |
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| `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">
**Reminder:** These are the _root-level_ options for the Admin Panel. You can
@@ -187,6 +188,12 @@ The following options are available:
| `graphQL` | `/graphql` | The [GraphQL API](../graphql/overview) base path. |
| `graphQLPlayground` | `/graphql-playground` | The GraphQL Playground. |
<Banner type="warning">
**Important:** Changing Root-level Routes also requires a change to [Project
Structure](#project-structure) to match the new route. [More
details](#customizing-root-level-routes).
</Banner>
<Banner type="success">
**Tip:** You can easily add _new_ routes to the Admin Panel through [Custom
Endpoints](../rest-api/overview#custom-endpoints) and [Custom
@@ -197,13 +204,29 @@ The following options are available:
You can change the Root-level Routes as needed, such as to mount the Admin Panel at the root of your application.
Changing Root-level Routes also requires a change to [Project Structure](#project-structure) to match the new route. For example, if you set `routes.admin` to `/`, you would need to completely remove the `admin` directory from the project structure:
This change, however, also requires a change to your [Project Structure](#project-structure) to match the new route.
For example, if you set `routes.admin` to `/`:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
routes: {
admin: '/', // highlight-line
},
})
```
Then you would need to completely remove the `admin` directory from the project structure:
```plaintext
app/
├─ (payload)/
├── [[...segments]]/
app
├─ (payload)
├── [[...segments]]
├──── ...
├── layout.tsx
```
<Banner type="warning">
@@ -276,3 +299,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

@@ -114,7 +114,12 @@ const MyComponent: React.FC = () => {
## useAllFormFields
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Your component will re-render when _any_ field changes, so use this hook only if you absolutely need to. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
<Banner type="warning">
**Warning:** Your component will re-render when _any_ field changes, so use
this hook only if you absolutely need to.
</Banner>
You can do lots of powerful stuff by retrieving the full form state, like using built-in helper functions to reduce field state to values only, or to retrieve sibling data by path.
@@ -734,7 +739,7 @@ The `useDocumentInfo` hook provides information about the current document being
| **`lastUpdateTime`** | Timestamp of the last update to the document. |
| **`mostRecentVersionIsAutosaved`** | Whether the most recent version is an autosaved version. |
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences. [More details](./preferences). |
| **`savedDocumentData`** | The saved data of the document. |
| **`data`** | The saved data of the document. |
| **`setDocFieldPreferences`** | Method to set preferences for a specific field. [More details](./preferences). |
| **`setDocumentTitle`** | Method to set the document title. |
| **`setHasPublishedDoc`** | Method to update whether the document has been published. |

View File

@@ -180,19 +180,22 @@ As Payload sets HTTP-only cookies, logging out cannot be done by just removing a
**Example REST API logout**:
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const res = await fetch(
'http://localhost:3000/api/[collection-slug]/logout?allSessions=false',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
})
)
```
**Example GraphQL Mutation**:
```
mutation {
logout[collection-singular-label]
logoutUser(allSessions: false)
}
```
@@ -203,6 +206,10 @@ mutation {
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
#### Logging out with sessions enabled
By default, logging out will only end the session pertaining to the JWT that was used to log out with. However, you can pass `allSessions: true` to the logout operation in order to end all sessions for the user logging out.
## Refresh
Allows for "refreshing" JWTs. If your user has a token that is about to expire, but the user is still active and using the app, you might want to use the `refresh` operation to receive a new token by executing this operation via the authenticated user.

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
@@ -91,6 +91,7 @@ The following options are available:
| **`strategies`** | Advanced - an array of custom authentication strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
| **`useSessions`** | True by default. Set to `false` to use stateless JWTs for authentication instead of sessions. |
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More details](./email#email-verification). |
### Login With Username
@@ -178,7 +179,7 @@ All auth-related operations are available via Payload's REST, Local, and GraphQL
## Strategies
Out of the box Payload ships with a three powerful Authentication strategies:
Out of the box Payload ships with three powerful Authentication strategies:
- [HTTP-Only Cookies](./cookies)
- [JSON Web Tokens (JWT)](./jwt)
@@ -201,3 +202,43 @@ API Keys can be enabled on auth collections. These are particularly useful when
### Custom Strategies
There are cases where these may not be enough for your application. Payload is extendable by design so you can wire up your own strategy when you need to. [More details](./custom-strategies).
### Access Control
Default auth fields including `email`, `username`, and `password` can be overridden by defining a custom field with the same name in your collection config. This allows you to customize the field — including access control — while preserving the underlying auth functionality. For example, you might want to restrict the `email` field from being updated once it is created, or only allow it to be read by certain user roles. You can achieve this by redefining the field and setting access rules accordingly.
Here's an example of how to restrict access to default auth fields:
```ts
import type { CollectionConfig } from 'payload'
export const Auth: CollectionConfig = {
slug: 'users',
auth: true,
fields: [
{
name: 'email', // or 'username'
type: 'text',
access: {
create: () => true,
read: () => false,
update: () => false,
},
},
{
name: 'password', // this will be applied to all password-related fields including new password, confirm password.
type: 'text',
hidden: true, // needed only for the password field to prevent duplication in the Admin panel
access: {
update: () => false,
},
},
],
}
```
**Note:**
- Access functions will apply across the application — I.e. if `read` access is disabled on `email`, it will not appear in the Admin panel UI or API.
- Restricting `read` on the `email` or `username` disables the **Unlock** action in the Admin panel as this function requires access to a user-identifying field.
- When overriding the `password` field, you may need to include `hidden: true` to prevent duplicate fields being displayed in the Admin panel.

View File

@@ -1,62 +0,0 @@
---
title: Project Configuration
label: Configuration
order: 20
desc: Quickly configure and deploy your Payload Cloud project in a few simple steps.
keywords: configuration, config, settings, project, cloud, payload cloud, deploy, deployment
---
## Select your plan
Once you have created a project, you will need to select your plan. This will determine the resources that are allocated to your project and the features that are available to you.
<Banner type="success">
Note: All Payload Cloud teams that deploy a project require a card on file.
This helps us prevent fraud and abuse on our platform. If you select a plan
with a free trial, you will not be charged until your trial period is over.
Well remind you 7 days before your trial ends and you can cancel anytime.
</Banner>
## Project Details
| Option | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Region** | Select the region closest to your audience. This will ensure the fastest communication between your data and your client. |
| **Project Name** | A name for your project. You can change this at any time. |
| **Project Slug** | Choose a unique slug to identify your project. This needs to be unique for your team and you can change it any time. |
| **Team** | Select the team you want to create the project under. If this is your first project, a personal team will be created for you automatically. You can modify your team settings and invite new members at any time from the Team Settings page. |
## Build Settings
If you are deploying a new project from a template, the following settings will be automatically configured for you. If you are using your own repository, you need to make sure your build settings are accurate for your project to deploy correctly.
| Option | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Root Directory** | The folder where your `package.json` file lives. |
| **Install Command** | The command used to install your modules, for example: `yarn install` or `npm install` |
| **Build Command** | The command used to build your application, for example: `yarn build` or `npm run build` |
| **Serve Command** | The command used to serve your application, for example: `yarn serve` or `npm run serve` |
| **Branch to Deploy** | Select the branch of your repository that you want to deploy from. This is the branch that will be used to build your project when you commit new changes. |
| **Default Domain** | Set a default domain for your project. This must be unique and you will not able to change it. You can always add a custom domain later in your project settings. |
## Environment Variables
Any of the features in Payload Cloud that require environment variables will automatically be provided to your application. If your app requires any custom environment variables, you can set them here.
<Banner type="warning">
Note: For security reasons, any variables you wish to provide to the [Admin
Panel](../admin/overview) must be prefixed with `NEXT_PUBLIC_`.  Learn more
[here](../configuration/environment-vars).
</Banner>
## Payment
Payment methods can be set per project and can be updated any time. You can use teams default payment method, or add a new one. Modify your payment methods in your Project settings / Team settings.
<Banner type="success">
**Note:** All Payload Cloud teams that deploy a project require a card on
file. This helps us prevent fraud and abuse on our platform. If you select a
plan with a free trial, you will not be charged until your trial period is
over. Well remind you 7 days before your trial ends and you can cancel
anytime.
</Banner>

View File

@@ -1,53 +0,0 @@
---
title: Getting Started
label: Getting Started
order: 10
desc: Get started with Payload Cloud, a deployment solution specifically designed for Node + MongoDB applications.
keywords: cloud, hosted, database, storage, email, deployment, serverless, node, mongodb, s3, aws, cloudflare, atlas, resend, payload, cms
---
A deployment solution specifically designed for Node.js + MongoDB applications, offering seamless deployment of your entire stack in one place. You can get started in minutes with a one-click template or bring your own codebase with you.
Payload Cloud offers various plans tailored to meet your specific needs, including a MongoDB Atlas database, S3 file storage, and email delivery powered by [Resend](https://resend.com). To see a full breakdown of features and plans, see our [Cloud Pricing page](https://payloadcms.com/cloud-pricing).
To get started, you first need to create an account. Head over to [the login screen](https://payloadcms.com/login) and **Register for Free**.
<Banner type="success">
To create your first project, you can either select [a
template](#starting-from-a-template) or [import an existing
project](#importing-from-an-existing-codebase) from GitHub.
</Banner>
## Starting from a Template
Templates come preconfigured and provide a one-click solution to quickly deploy a new application.
![Screen for creating a new project from a template](https://payloadcms.com/images/docs/cloud/create-from-template.jpg)
_Creating a new project from a template._
After creating an account, select your desired template from the Projects page. At this point, you need to connect to authorize the Payload Cloud application with your GitHub account. Click Continue with GitHub and follow the prompts to authorize the app.
Next, select your `GitHub Scope`. If you belong to multiple organizations, they will show up here. If you do not see the organization you are looking for, you may need to adjust your GitHub app permissions.
After selecting your scope, create a unique `repository name` and select whether you want your repository to be public or private on GitHub.
<Banner type="warning">
**Note:** Public repositories can be accessed by anyone online, while private
repositories grant access only to you and anyone you explicitly authorize.
</Banner>
Once you are ready, click **Create Project**. This will clone the selected template to a new repository in your GitHub account, and take you to the configuration page to set up your project for deployment.
## Importing from an Existing Codebase
Payload Cloud works for any Node.js + MongoDB app. From the New Project page, select **import an existing Git codebase**. Choose the organization and select the repository you want to import. From here, you will be taken to the configuration page to set up your project for deployment.
![Screen for creating a new project from an existing repository](https://payloadcms.com/images/docs/cloud/create-from-existing.jpg)
_Creating a new project from an existing repository._
<Banner type="warning">
**Note:** In order to make use of the features of Payload Cloud in your own
codebase, you will need to add the [Cloud
Plugin](https://github.com/payloadcms/payload/tree/main/packages/payload-cloud)
to your Payload app.
</Banner>

View File

@@ -1,137 +0,0 @@
---
title: Cloud Projects
label: Projects
order: 40
desc: Manage your Payload Cloud projects.
keywords: cloud, payload cloud, projects, project, overview, database, file storage, build settings, environment variables, custom domains, email, developing locally
---
## Overview
<Banner>
The overview tab shows your most recent deployment, along with build and
deployment logs. From here, you can see your live URL, deployment details like
timestamps and commit hash, as well as the status of your deployment. You can
also trigger a redeployment manually, which will rebuild your project using
the current configuration.
</Banner>
![Payload Cloud Overview Page](https://payloadcms.com/images/docs/cloud/overview-page.jpg)
_A screenshot of the Overview page for a Cloud project._
## Database
Your Payload Cloud project comes with a MongoDB serverless Atlas DB instance or a Dedicated Atlas cluster, depending on your plan. To interact with your cloud database, you will be provided with a MongoDB connection string. This can be found under the **Database** tab of your project.
`mongodb+srv://your_connection_string`
## File Storage
Payload Cloud gives you S3 file storage backed by Cloudflare as a CDN, and this plugin extends Payload so that all of your media will be stored in S3 rather than locally.
AWS Cognito is used for authentication to your S3 bucket. The [Payload Cloud Plugin](https://github.com/payloadcms/payload/tree/main/packages/payload-cloud) will automatically pick up these values. These values are only if you'd like to access your files directly, outside of Payload Cloud.
### Accessing Files Outside of Payload Cloud
If you'd like to access your files outside of Payload Cloud, you'll need to retrieve some values from your project's settings and put them into your environment variables. In Payload Cloud, navigate to the File Storage tab and copy the values using the copy button. Put these values in your .env file. Also copy the Cognito Password value separately and put into your .env file as well.
When you are done, you should have the following values in your .env file:
```env
PAYLOAD_CLOUD=true
PAYLOAD_CLOUD_ENVIRONMENT=prod
PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID=
PAYLOAD_CLOUD_COGNITO_USER_POOL_ID=
PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID=
PAYLOAD_CLOUD_PROJECT_ID=
PAYLOAD_CLOUD_BUCKET=
PAYLOAD_CLOUD_BUCKET_REGION=
PAYLOAD_CLOUD_COGNITO_PASSWORD=
```
The plugin will pick up these values and use them to access your files.
## Build Settings
You can update settings from your Projects Settings tab. Changes to your build settings will trigger a redeployment of your project.
## Environment Variables
From the Environment Variables page of the Settings tab, you can add, update and delete variables for use in your project. Like build settings, these changes will trigger a redeployment of your project.
<Banner>
Note: For security reasons, any variables you wish to provide to the [Admin
Panel](../admin/overview) must be prefixed with `NEXT_PUBLIC_`. [More
details](../configuration/environment-vars).
</Banner>
## Custom Domains
With Payload Cloud, you can add custom domain names to your project. To do so, first go to the Domains page of the Settings tab of your project. Here you can see your default domain. To add a new domain, type in the domain name you wish to use.
<Banner>
Note: do not include the protocol (http:// or https://) or any paths (/page).
Only include the domain name and extension, and optionally a subdomain. -
your-domain.com - backend.your-domain.com
</Banner>
Once you click save, a DNS record will be generated for your domain name to point to your live project. Add this record into your DNS providers records, and once the records are resolving properly (this can take 1hr to 48hrs in some cases), your domain will now to point to your live project.
You will also need to configure your Payload project to use your specified domain. In your `payload.config.ts` file, specify your `serverURL` with your domain:
```ts
export default buildConfig({
serverURL: 'https://example.com',
// the rest of your config,
})
```
## Email
Powered by [Resend](https://resend.com), Payload Cloud comes with integrated email support out of the box. No configuration is needed, and you can use `payload.sendEmail()` to send email right from your Payload app. To learn more about sending email with Payload, checkout the [Email Configuration](../email/overview) overview.
If you are on the Pro or Enterprise plan, you can add your own custom Email domain name. From the Email page of your projects Settings, add the domain you wish to use for email delivery. This will generate a set of DNS records. Add these records to your DNS provider and click verify to check that your records are resolving properly. Once verified, your emails will now be sent from your custom domain name.
## Developing Locally
To make changes to your project, you will need to clone the repository defined in your project settings to your local machine. In order to run your project locally, you will need configure your local environment first. Refer to your repositorys `README.md` file to see the steps needed for your specific template.
From there, you are ready to make updates to your project. When you are ready to make your changes live, commit your changes to the branch you specified in your Project settings, and your application will automatically trigger a redeploy and build from your latest commit.
## Cloud Plugin
Projects generated from a template will come pre-configured with the official Cloud Plugin, but if you are using your own repository you will need to add this into your project. To do so, add the plugin to your Payload Config:
`pnpm add @payloadcms/payload-cloud`
```js
import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
import { buildConfig } from 'payload'
export default buildConfig({
plugins: [payloadCloudPlugin()],
// rest of config
})
```
<Banner type="warning">
**Note:** If your Payload Config already has an email with transport, this
will take precedence over Payload Cloud's email service.
</Banner>
<Banner type="info">
Good to know: the Payload Cloud Plugin was previously named
`@payloadcms/plugin-cloud`. If you are using this plugin, you should update to
the new package name.
</Banner>
#### **Optional configuration**
If you wish to opt-out of any Payload cloud features, the plugin also accepts options to do so.
```js
payloadCloud({
storage: false, // Disable file storage
email: false, // Disable email delivery
})
```

View File

@@ -1,35 +0,0 @@
---
title: Cloud Teams
label: Teams
order: 30
desc: Manage your Payload Cloud team and billing settings.
keywords: team, teams, billing, subscription, payment, plan, plans, cloud, payload cloud
---
<Banner>
Within Payload Cloud, the team management feature offers you the ability to
manage your organization, team members, billing, and subscription settings.
</Banner>
![Payload Cloud Team Settings](https://payloadcms.com/images/docs/cloud/team-settings.jpg)
_A screenshot of the Team Settings page._
## Members
Each team has members that can interact with your projects. You can invite multiple people to your team and each individual can belong to more than one team. You can assign them either `owner` or `user` permissions. Owners are able to make admin-only changes, such as deleting projects, and editing billing information.
## Adding Members
To add a new member to your team, visit your Teams Settings page, and click “Invite Teammate”. You can then add their email address, and assign their role. Press “Save” to send the invitations, which will send an email to the invited team member where they can create a new account.
## Billing
Users can update billing settings and subscriptions for any teams where they are designated as an `owner`. To make updates to the teams payment methods, visit the Billing page under the Team Settings tab. You can add new cards, delete cards, and set a payment method as a default. The default payment method will be used in the event that another payment method fails.
## Subscriptions
From the Subscriptions page, a team owner can see all current plans for their team. From here, you can see the price of each plan, if there is an active trial, and when you will be billed next.
## Invoices
The Invoices page will you show you the invoices for your account, as well as the status on their payment.

View File

@@ -79,12 +79,14 @@ The following options are available:
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| `timestamps` | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| `trash` | A boolean to enable soft deletes for this collection. Defaults to `false`. [More details](../trash/overview). |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `upload` | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
| `indexes` | Define compound indexes for this collection. This can be used to either speed up querying/sorting by 2 or more fields at the same time or to ensure uniqueness between several fields. |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks. [More details](../queries/select). |
| `disableBulkEdit` | Disable the bulk edit operation for the collection in the admin panel and the REST API |
_\* An asterisk denotes that a property is required._
@@ -120,26 +122,35 @@ export const MyCollection: CollectionConfig = {
The following options are available:
| Option | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. |
| `hooks` | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. A field with `virtual: true` cannot be used as the title, unless it's linked to a relationship'. |
| `description` | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| `disableCopyToLocale` | Disables the "Copy to Locale" button while editing documents within this Collection. Only applicable when localization is enabled. |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `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). |
| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
| Option | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. |
| `hooks` | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. |
| `description` | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| `disableCopyToLocale` | Disables the "Copy to Locale" button while editing documents within this Collection. Only applicable when localization is enabled. |
| `groupBy` | Beta. Enable grouping by a field in the list view. |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `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). |
| `enableListViewSelectAPI` | Performance opt-in. When `true`, uses the Select API in the List View to query only the active columns as opposed to entire documents. [More details](#enable-list-view-select-api). |
| `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">
**Note:** If you set `useAsTitle` to a relationship or join field, it will use
only the ID of the related document(s) as the title. To display a specific
field (i.e. title) from the related document instead, create a virtual field
that extracts the desired data, and set `useAsTitle` to that virtual field.
</Banner>
### Custom Components
@@ -262,6 +273,66 @@ export const Posts: CollectionConfig = {
these fields so your admin queries can remain performant.
</Banner>
## Enable List View Select API
When `true`, the List View will use the [Select API](../queries/select) to query only the _active_ columns as opposed to entire documents. This can greatly improve performance, especially for collections with large documents or many fields.
To enable this, set `enableListViewSelectAPI: true` in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// ...
// highlight-start
enableListViewSelectAPI: true,
// highlight-end
},
}
```
<Banner type="info">
**Note:** The `enableListViewSelectAPI` property is labeled as experimental,
as it will likely become the default behavior in v4 and be deprecated.
</Banner>
Enabling this feature may cause unexpected behavior in some cases, however, such as when using hooks that rely on the full document data.
For example, if your component relies on a "title" field, this field will no longer be populated if the column is inactive:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
hooks: {
afterRead: [
({ doc }) => doc.title, // The `title` field will no longer be populated by default, unless the column is active
],
},
},
],
}
```
To ensure title is always present, you will need to add that field to the [`forceSelect`](../queries/select) property in your Collection Config:
```ts
export const Posts: CollectionConfig = {
// ...
forceSelect: {
title: true,
},
}
```
## GraphQL
You can completely disable GraphQL for this collection by passing `graphQL: false` to your collection config. This will completely disable all queries, mutations, and types from appearing in your GraphQL schema.

View File

@@ -1,7 +1,7 @@
---
title: Environment Variables
label: Environment Variables
order: 100
order: 60
desc: Learn how to use Environment Variables in your Payload project
---
@@ -72,7 +72,7 @@ const MyClientComponent = () => {
}
```
For more information, check out the [Next.js Documentation](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables).
For more information, check out the [Next.js documentation](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables).
## Outside of Next.js

View File

@@ -84,7 +84,7 @@ The following options are available:
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Global. |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#global-config). |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks. [More details](../queries/select). |
_\* An asterisk denotes that a property is required._

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). |
@@ -110,7 +111,7 @@ _\* An asterisk denotes that a property is required._
details](../custom-components/overview#accessing-the-payload-config).
</Banner>
### Typescript Config
### TypeScript Config
Payload exposes a variety of TypeScript settings that you can leverage. These settings are used to auto-generate TypeScript interfaces for your [Collections](./collections) and [Globals](./globals), and to ensure that Payload uses your [Generated Types](../typescript/overview) for all [Local API](../local-api/overview) methods.
@@ -121,10 +122,11 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
typescript: {
// highlight-line
// ...
},
// highlight-end
})
```
@@ -227,7 +229,9 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
cors: '*', // highlight-line
// highlight-start
cors: '*',
// highlight-end
})
```

View File

@@ -51,7 +51,7 @@ For more granular control, pass a configuration object instead. Payload exposes
| Property | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Component` \* | Pass in the component path that should be rendered when a user navigates to this route. |
| `path` \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
| `path` \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. Must begin with a forward slash (`/`). |
| `exact` | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
| `strict` | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
| `sensitive` | When true, will match if the path is case sensitive. |
@@ -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

@@ -30,7 +30,6 @@ export const MyCollectionOrGlobalConfig: CollectionConfig = {
// - api
// - versions
// - version
// - livePreview
// - [key: string]
// See below for more details
},
@@ -88,7 +87,7 @@ export const MyCollection: CollectionConfig = {
### Edit View
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. the Edit View is keyed under the `default` property in the `views.edit` object.
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. The Edit View is keyed under the `default` property in the `views.edit` object.
For more information on customizing the Edit View, see the [Edit View](./edit-view) documentation.
@@ -107,8 +106,8 @@ export const MyCollection: CollectionConfig = {
components: {
views: {
edit: {
myCustomTab: {
Component: '/path/to/MyCustomTab',
myCustomView: {
Component: '/path/to/MyCustomView',
path: '/my-custom-tab',
// highlight-start
tab: {
@@ -116,13 +115,14 @@ export const MyCollection: CollectionConfig = {
},
// highlight-end
},
anotherCustomTab: {
anotherCustomView: {
Component: '/path/to/AnotherCustomView',
path: '/another-custom-view',
// highlight-start
tab: {
label: 'Another Custom View',
href: '/another-custom-view',
order: '100',
},
// highlight-end
},
@@ -143,6 +143,7 @@ The following options are available for tabs:
| ----------- | ------------------------------------------------------------------------------------------------------------- |
| `label` | The label to display in the tab. |
| `href` | The URL to navigate to when the tab is clicked. This is optional and defaults to the tab's `path`. |
| `order` | The order in which the tab appears in the navigation. Can be set on default and custom tabs. |
| `Component` | The component to render in the tab. This can be a Server or Client component. [More details](#tab-components) |
### Tab Components

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

@@ -505,3 +505,51 @@ Payload also exports its [SCSS](https://sass-lang.com) library for reuse which i
**Note:** You can also drill into Payload's own component styles, or easily
apply global, app-wide CSS. More on that [here](../admin/customizing-css).
</Banner>
## Performance
An often overlooked aspect of Custom Components is performance. If unchecked, Custom Components can lead to slow load times of the Admin Panel and ultimately a poor user experience.
This is different from front-end performance of your public-facing site.
<Banner type="success">
For more performance tips, see the [Performance
documentation](../performance/overview).
</Banner>
### Follow React and Next.js best practices
All Custom Components are built using [React](https://react.dev). For this reason, it is important to follow React best practices. This includes using memoization, streaming, caching, optimizing renders, using hooks appropriately, and more.
To learn more, see the [React documentation](https://react.dev/learn).
The Admin Panel itself is a [Next.js](https://nextjs.org) application. For this reason, it is _also_ important to follow Next.js best practices. This includes bundling, when to use layouts vs pages, where to place the server/client boundary, and more.
To learn more, see the [Next.js documentation](https://nextjs.org/docs).
### Reducing initial HTML size
With Server Components, be aware of what is being sent to through the server/client boundary. All props are serialized and sent through the network. This can lead to large HTML sizes and slow initial load times if too much data is being sent to the client.
To minimize this, you must be explicit about what props are sent to the client. Prefer server components and only send the necessary props to the client. This will also offset some of the JS execution to the server.
<Banner type="success">
**Tip:** Use [React Suspense](https://react.dev/reference/react/Suspense) to
progressively load components and improve perceived performance.
</Banner>
### Prevent unnecessary re-renders
If subscribing your component to form state, it may be re-rendering more often than necessary.
To do this, use the [`useFormFields`](../admin/react-hooks) hook instead of `useFields` when you only need to access specific fields.
```ts
'use client'
import { useFormFields } from '@payloadcms/ui'
const MyComponent: TextFieldClientComponent = ({ path }) => {
const value = useFormFields(([fields, dispatch]) => fields[path])
// ...
}
```

View File

@@ -372,13 +372,13 @@ export default function MyCustomLogo() {
}
```
### Header
### header
The `Header` property allows you to inject Custom Components above the Payload header.
The `header` property allows you to inject Custom Components above the Payload header.
Examples of a custom header components might include an announcements banner, a notifications bar, or anything else you'd like to display at the top of the Admin Panel in a prominent location.
To add `Header` components, use the `admin.components.header` property in your Payload Config:
To add `header` components, use the `admin.components.header` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
@@ -388,14 +388,14 @@ export default buildConfig({
admin: {
// highlight-start
components: {
Header: ['/path/to/your/component'],
header: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `Header` component:
Here is an example of a simple `header` component:
```tsx
export default function MyCustomHeader() {

84
docs/database/indexes.mdx Normal file
View File

@@ -0,0 +1,84 @@
---
title: Indexes
label: Indexes
order: 40
keywords: database, indexes
desc: Index fields to produce faster queries.
---
Database indexes are a way to optimize the performance of your database by allowing it to quickly locate and retrieve data. If you have a field that you frequently query or sort by, adding an index to that field can significantly improve the speed of those operations.
When your query runs, the database will not scan the entire document to find that one field, but will instead use the index to quickly locate the data.
To index a field, set the `index` option to `true` in your field's config:
```ts
import type { CollectionConfig } from 'payload'
export MyCollection: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'title',
type: 'text',
// highlight-start
index: true,
// highlight-end
},
]
}
```
<Banner type="info">
**Note:** The `id`, `createdAt`, and `updatedAt` fields are indexed by
default.
</Banner>
<Banner type="success">
**Tip:** If you're using MongoDB, you can use [MongoDB
Compass](https://www.mongodb.com/products/compass) to visualize and manage
your indexes.
</Banner>
## Compound Indexes
In addition to indexing single fields, you can also create compound indexes that index multiple fields together. This can be useful for optimizing queries that filter or sort by multiple fields.
To create a compound index, use the `indexes` option in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
fields: [
// ...
],
indexes: [
{
fields: ['title', 'createdAt'],
unique: true, // Optional, if you want the combination of fields to be unique
},
],
}
```
## 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

@@ -1,7 +1,7 @@
---
title: MongoDB
label: MongoDB
order: 40
order: 50
desc: Payload has supported MongoDB natively since we started. The flexible nature of MongoDB lends itself well to Payload's powerful fields.
keywords: MongoDB, documentation, typescript, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -30,17 +30,22 @@ export default buildConfig({
## Options
| Option | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
| `collectionsSchemaOptions` | Customize Mongoose schema options for collections. |
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
| `migrationDir` | Customize the directory that migrations are stored. |
| `transactionOptions` | An object with configuration properties used in [transactions](https://www.mongodb.com/docs/manual/core/transactions/) or `false` which will disable the use of transactions. |
| `collation` | Enable language-specific string comparison with customizable options. Available on MongoDB 3.4+. Defaults locale to "en". Example: `{ strength: 3 }`. For a full list of collation options and their definitions, see the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/collation/). |
| `allowAdditionalKeys` | By default, Payload strips all additional keys from MongoDB data that don't exist in the Payload schema. If you have some data that you want to include to the result but it doesn't exist in Payload, you can set this to `true`. Be careful as Payload access control _won't_ work for this data. |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| Option | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
| `collectionsSchemaOptions` | Customize Mongoose schema options for collections. |
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
| `migrationDir` | Customize the directory that migrations are stored. |
| `transactionOptions` | An object with configuration properties used in [transactions](https://www.mongodb.com/docs/manual/core/transactions/) or `false` which will disable the use of transactions. |
| `collation` | Enable language-specific string comparison with customizable options. Available on MongoDB 3.4+. Defaults locale to "en". Example: `{ strength: 3 }`. For a full list of collation options and their definitions, see the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/collation/). |
| `allowAdditionalKeys` | By default, Payload strips all additional keys from MongoDB data that don't exist in the Payload schema. If you have some data that you want to include to the result but it doesn't exist in Payload, you can set this to `true`. Be careful as Payload access control _won't_ work for this data. |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `disableFallbackSort` | Set to `true` to disable the adapter adding a fallback sort when sorting by non-unique fields, this can affect performance in some cases but it ensures a consistent order of results. |
| `useAlternativeDropDatabase` | Set to `true` to use an alternative `dropDatabase` implementation that calls `collection.deleteMany({})` on every collection instead of sending a raw `dropDatabase` command. Payload only uses `dropDatabase` for testing purposes. Defaults to `false`. |
| `useBigIntForNumberIDs` | Set to `true` to use `BigInt` for custom ID fields of type `'number'`. Useful for databases that don't support `double` or `int32` IDs. Defaults to `false`. |
| `useJoinAggregations` | Set to `false` to disable join aggregations (which use correlated subqueries) and instead populate join fields via multiple `find` queries. Defaults to `true`. |
| `usePipelineInSortLookup` | Set to `false` to disable the use of `pipeline` in the `$lookup` aggregation in sorting. Defaults to `true`. |
## Access to Mongoose models
@@ -55,9 +60,21 @@ You can access Mongoose models as follows:
## Using other MongoDB implementations
Limitations with [DocumentDB](https://aws.amazon.com/documentdb/) and [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db):
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):
- For Azure Cosmos DB you must pass `transactionOptions: false` to the adapter options. Azure Cosmos DB does not support transactions that update two and more documents in different collections, which is a common case when using Payload (via hooks).
- For Azure Cosmos DB the root config property `indexSortableFields` must be set to `true`.
- The [Join Field](../fields/join) is not supported in DocumentDB and Azure Cosmos DB, as we internally use MongoDB aggregations to query data for that field, which are limited there. This can be changed in the future.
- For DocumentDB pass `disableIndexHints: true` to disable hinting to the DB to use `id` as index which can cause problems with DocumentDB.
```ts
import { mongooseAdapter, compatibilityOptions } from '@payloadcms/db-mongodb'
export default buildConfig({
db: mongooseAdapter({
url: process.env.DATABASE_URI,
// For example, if you're using firestore:
...compatibilityOptions.firestore,
}),
})
```
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

@@ -1,7 +1,7 @@
---
title: Postgres
label: Postgres
order: 50
order: 60
desc: Payload supports Postgres through an officially supported Drizzle Database Adapter.
keywords: Postgres, documentation, typescript, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -81,6 +81,7 @@ export default buildConfig({
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `readReplicas` | An array of DB read replicas connection strings, can be used to offload read-heavy traffic. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle

View File

@@ -1,7 +1,7 @@
---
title: SQLite
label: SQLite
order: 60
order: 70
desc: Payload supports SQLite through an officially supported Drizzle Database Adapter.
keywords: SQLite, documentation, typescript, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -50,6 +50,7 @@ export default buildConfig({
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `autoIncrement` | Pass `true` to enable SQLite [AUTOINCREMENT](https://www.sqlite.org/autoinc.html) for primary keys to ensure the same ID cannot be reused from deleted rows |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle

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

@@ -41,17 +41,17 @@ export const MyArrayField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. |
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More](/docs/fields/overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More details](/docs/fields/overview#validation). |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
| **`required`** | Require this field to have a value. |
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |

View File

@@ -41,17 +41,17 @@ export const MyBlocksField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin Panel. |
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |

View File

@@ -30,15 +30,15 @@ export const MyCheckboxField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). |

View File

@@ -31,18 +31,18 @@ export const MyBlocksField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |

View File

@@ -30,15 +30,15 @@ export const MyDateField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |

View File

@@ -30,16 +30,16 @@ export const MyEmailField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |

View File

@@ -35,15 +35,15 @@ export const MyGroupField: Field = {
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`fields`** \* | Array of field types to nest within this Group. |
| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. Defaults to the field name, if defined. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide an object of data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide an object of data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Group will be kept, so there is no need to specify each nested field as `localized`. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |

View File

@@ -135,7 +135,7 @@ powerful Admin UI.
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when retrieved from the database. [More](./overview#field-names) |
| **`name`** \* | To be used as the property name when retrieved from the database. [More details](./overview#field-names). |
| **`collection`** \* | The `slug`s having the relationship field or an array of collection slugs. |
| **`on`** \* | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. If `collection` is an array, this field must exist for all specified collections |
| **`orderable`** | If true, enables custom ordering and joined documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
@@ -296,11 +296,16 @@ query {
sort: "createdAt"
limit: 5
where: { author: { equals: "66e3431a3f23e684075aaeb9" } }
"""
Optionally pass count: true if you want to retrieve totalDocs
"""
count: true -- s
) {
docs {
title
}
hasNextPage
totalDocs
}
}
}

View File

@@ -31,17 +31,17 @@ export const MyJSONField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |

View File

@@ -30,7 +30,7 @@ export const MyNumberField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`min`** | Minimum value accepted. Used in the default `validation` function. |
| **`max`** | Maximum value accepted. Used in the default `validation` function. |
@@ -38,13 +38,13 @@ export const MyNumberField: Field = {
| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. |
| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |

View File

@@ -157,6 +157,7 @@ The following field names are forbidden and cannot be used:
- `salt`
- `hash`
- `file`
- `status` - with Postgres Adapter and when drafts are enabled
### Field-level Hooks
@@ -303,7 +304,7 @@ The following additional properties are provided in the `ctx` object:
| `path` | The full path to the field in the schema, represented as an array of string segments, including array indexes. I.e `['group', 'myArray', '1', 'textField']`. |
| `id` | The `id` of the current document being edited. `id` is `undefined` during the `create` operation. |
| `req` | The current HTTP request object. Contains `payload`, `user`, etc. |
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#async-field-validations). |
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#validation-performance). |
#### Localized and Built-in Error Messages
@@ -315,7 +316,8 @@ import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
validate: (value, {req: { t }}) => Boolean(value) || t('validation:required'), // highlight-line
validate: (value, { req: { t } }) =>
Boolean(value) || t('validation:required'), // highlight-line
}
```
@@ -350,7 +352,6 @@ import {
code,
date,
email,
group,
json,
number,
point,
@@ -365,11 +366,11 @@ import {
} from 'payload/shared'
```
#### Async Field Validations
#### Validation Performance
Custom validation functions can also be asynchronous depending on your needs. This makes it possible to make requests to external services or perform other miscellaneous asynchronous logic.
When writing async or computationally heavy validation functions, it is important to consider the performance implications. Within the Admin Panel, validations are executed on every change to the field, so they should be as lightweight as possible and only run when necessary.
When writing async validation functions, it is important to consider the performance implications. Validations are executed on every change to the field, so they should be as lightweight as possible. If you need to perform expensive validations, such as querying the database, consider using the `event` property in the `ctx` object to only run the validation on form submission.
If you need to perform expensive validations, such as querying the database, consider using the `event` property in the `ctx` object to only run that particular validation on form submission.
To write asynchronous validation functions, use the `async` keyword to define your function:
@@ -403,6 +404,11 @@ export const Orders: CollectionConfig = {
}
```
<Banner type="success">
For more performance tips, see the [Performance
documentation](../performance/overview).
</Banner>
## Custom ID Fields
All [Collections](../configuration/collections) automatically generate their own ID field. If needed, you can override this behavior by providing an explicit ID field to your config. This field should either be required or have a hook to generate the ID dynamically.

View File

@@ -34,16 +34,16 @@ export const MyPointField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Used as a field label in the Admin Panel and to name the generated GraphQL type. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). |

View File

@@ -35,16 +35,16 @@ export const MyRadioField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |

View File

@@ -39,22 +39,22 @@ export const MyRelationshipField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More details](#filtering-relationship-options). |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxDepth`** | Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/queries/depth#max-depth) |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
@@ -93,7 +93,7 @@ The Relationship Field inherits all of the default admin options from the base [
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany` is set to `true`). |
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sort-options) |
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More details](#sort-options) |
| **`placeholder`** | Define a custom text or function to replace the generic default placeholder |
| **`appearance`** | Set to `drawer` or `select` to change the behavior of the field. Defaults to `select`. |

View File

@@ -23,14 +23,14 @@ Instead, you can invest your time and effort into learning the underlying open-s
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](./overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](./overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](./overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](./overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](./overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](./overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |

View File

@@ -35,18 +35,18 @@ export const MySelectField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-options) for more details. |
@@ -54,7 +54,7 @@ export const MySelectField: Field = {
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`filterOptions`** | Dynamically filter which options are available based on the user, data, etc. [More details](#filterOptions) |
| **`filterOptions`** | Dynamically filter which options are available based on the user, data, etc. [More details](#filteroptions) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |

View File

@@ -45,7 +45,7 @@ Each tab must have either a `name` or `label` and the required `fields` array. Y
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** | Groups field data into an object when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. |
| **`fields`** \* | The fields to render within this tab. |
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |

View File

@@ -30,18 +30,18 @@ export const MyTextField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |

View File

@@ -30,18 +30,18 @@ export const MyTextareaField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |

View File

@@ -32,8 +32,8 @@ export const MyUIField: Field = {
| ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **`name`** \* | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](./overview#field) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](./overview#cell) |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More details](./overview#field). |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More details](./overview#cell). |
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |

View File

@@ -46,23 +46,23 @@ export const MyUploadField: Field = {
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`relationTo`** \* | Provide a single collection `slug` to allow this field to accept a relation to. **Note: the related collection must be configured to support Uploads.** |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-upload-options). |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More details](#filtering-upload-options). |
| **`hasMany`** | Boolean which, if set to true, allows this field to have many relations instead of only one. |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with hasMany. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with hasMany. |
| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](../queries/depth) |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More details](/docs/upload/overview#collection-upload-options). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [Admin Options](./overview#admin-options). |

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

@@ -16,14 +16,15 @@ The labels you provide for your Collections and Globals are used to name the Gra
At the top of your Payload Config you can define all the options to manage GraphQL.
| Option | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
| `validationRules` | A function that takes the ExecutionArgs and returns an array of ValidationRules. |
| Option | Description |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground in production environments, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
| `disableIntrospectionInProduction` | A boolean that if false will enable the GraphQL introspection in production environments, defaults to true. |
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
| `validationRules` | A function that takes the ExecutionArgs and returns an array of ValidationRules. |
## Collections

View File

@@ -160,6 +160,7 @@ The following arguments are provided to the `afterChange` hook:
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`doc`** | The resulting Document after changes are applied. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`previousDoc`** | The Document before changes were applied. |

View File

@@ -128,6 +128,7 @@ The following arguments are provided to the `afterChange` hook:
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`doc`** | The resulting Document after changes are applied. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |

View File

@@ -93,12 +93,108 @@ All Hooks can be written as either synchronous or asynchronous functions. Choosi
#### Asynchronous
If the Hook should modify data before a Document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function. This way you can be sure that your Hook completes before the operation's lifecycle continues. Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts.
If the Hook should modify data before a Document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function. This way you can be sure that your Hook completes before the operation's lifecycle continues.
Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts.
<Banner type="success">
**Tip:** If your hook executes a long-running task that doesn't affect the
response in any way, consider [offloading it to the job
queue](#offloading-long-running-tasks). That will free up the request to
continue processing without waiting for the task to complete.
</Banner>
#### Synchronous
If your Hook simply performs a side-effect, such as updating a CRM, it might be okay to define it synchronously, so the Payload operation does not have to wait for your hook to complete.
If your Hook simply performs a side-effect, such as mutating document data, it might be okay to define it synchronously, so the Payload operation does not have to wait for your hook to complete.
## Server-only Execution
Hooks are only triggered on the server and are automatically excluded from the client-side bundle. This means that you can safely use sensitive business logic in your Hooks without worrying about exposing it to the client.
## Performance
Hooks are a powerful way to customize the behavior of your APIs, but some hooks are run very often and can add significant overhead to your requests if not optimized.
When building hooks, combine together as many of these strategies as possible to ensure your hooks are as performant as they can be.
<Banner type="success">
For more performance tips, see the [Performance
documentation](../performance/overview).
</Banner>
### Writing efficient hooks
Consider when hooks are run. One common pitfall is putting expensive logic in hooks that run very often.
For example, the `read` operation runs on every read request, so avoid putting expensive logic in a `beforeRead` or `afterRead` hook.
```ts
{
hooks: {
beforeRead: [
async () => {
// This runs on every read request - avoid expensive logic here
await doSomethingExpensive()
return data
},
],
},
}
```
Instead, you might want to use a `beforeChange` or `afterChange` hook, which only runs when a document is created or updated.
```ts
{
hooks: {
beforeChange: [
async ({ context }) => {
// This is more acceptable here, although still should be mindful of performance
await doSomethingExpensive()
// ...
},
]
},
}
```
### Using Hook Context
Use [Hook Context](./context) avoid prevent infinite loops or avoid repeating expensive operations across multiple hooks in the same request.
```ts
{
hooks: {
beforeChange: [
async ({ context }) => {
const somethingExpensive = await doSomethingExpensive()
context.somethingExpensive = somethingExpensive
// ...
},
],
},
}
```
To learn more, see the [Hook Context documentation](./context).
### Offloading to the jobs queue
If your hooks perform any long-running tasks that don't direct affect request lifecycle, consider offloading them to the [jobs queue](../jobs-queue/overview). This will free up the request to continue processing without waiting for the task to complete.
```ts
{
hooks: {
afterChange: [
async ({ doc, req }) => {
// Offload to job queue
await req.payload.jobs.queue(...)
// ...
},
],
},
}
```
To learn more, see the [Job Queue documentation](../jobs-queue/overview).

View File

@@ -34,20 +34,20 @@ npm i @payloadcms/plugin-csm
Then in the `plugins` array of your Payload Config, call the plugin and enable any collections that require Content Source Maps.
```ts
import { buildConfig } from "payload/config"
import contentSourceMaps from "@payloadcms/plugin-csm"
import { buildConfig } from 'payload/config'
import contentSourceMaps from '@payloadcms/plugin-csm'
const config = buildConfig({
collections: [
{
slug: "pages",
slug: 'pages',
fields: [
{
name: 'slug',
type: 'text',
},
{
name: 'title,'
name: 'title',
type: 'text',
},
],
@@ -55,7 +55,7 @@ const config = buildConfig({
],
plugins: [
contentSourceMaps({
collections: ["pages"],
collections: ['pages'],
}),
],
})

View File

@@ -51,7 +51,7 @@ export default buildConfig({
// add as many cron jobs as you want
],
shouldAutoRun: async (payload) => {
// Tell Payload if it should run jobs or not.
// Tell Payload if it should run jobs or not. This function is optional and will return true by default.
// This function will be invoked each time Payload goes to pick up and run jobs.
// If this function ever returns false, the cron schedule will be stopped.
return true
@@ -82,6 +82,12 @@ await fetch('/api/payload-jobs/run?limit=100&queue=nightly', {
This endpoint is automatically mounted for you and is helpful in conjunction with serverless platforms like Vercel, where you might want to use Vercel Cron to invoke a serverless function that executes your jobs.
**Query Parameters:**
- `limit`: The maximum number of jobs to run in this invocation (default: 10).
- `queue`: The name of the queue to run jobs from. If not specified, jobs will be run from the `default` queue.
- `allQueues`: If set to `true`, all jobs from all queues will be run. This will ignore the `queue` parameter.
**Vercel Cron Example**
If you're deploying on Vercel, you can add a `vercel.json` file in the root of your project that configures Vercel Cron to invoke the `run` endpoint on a cron schedule.
@@ -139,11 +145,15 @@ If you want to process jobs programmatically from your server-side code, you can
**Run all jobs:**
```ts
// Run all jobs from the `default` queue - default limit is 10
const results = await payload.jobs.run()
// You can customize the queue name and limit by passing them as arguments:
await payload.jobs.run({ queue: 'nightly', limit: 100 })
// Run all jobs from all queues:
await payload.jobs.run({ allQueues: true })
// You can provide a where clause to filter the jobs that should be run:
await payload.jobs.run({
where: { 'input.message': { equals: 'secret' } },
@@ -160,10 +170,22 @@ const results = await payload.jobs.runByID({
### Bin script
Finally, you can process jobs via the bin script that comes with Payload out of the box.
Finally, you can process jobs via the bin script that comes with Payload out of the box. By default, this script will run jobs from the `default` queue, with a limit of 10 jobs per invocation:
```sh
npx payload jobs:run --queue default --limit 10
npx payload jobs:run
```
You can override the default queue and limit by passing the `--queue` and `--limit` flags:
```sh
npx payload jobs:run --queue myQueue --limit 15
```
If you want to run all jobs from all queues, you can pass the `--all-queues` flag:
```sh
npx payload jobs:run --all-queues
```
In addition, the bin script allows you to pass a `--cron` flag to the `jobs:run` command to run the jobs on a scheduled, cron basis:

View File

@@ -0,0 +1,155 @@
---
title: Job Schedules
label: Schedules
order: 60
desc: Payload allows you to schedule jobs to run periodically
keywords: jobs queue, application framework, typescript, node, react, nextjs, scheduling, cron, schedule
---
Payload's `schedule` property lets you enqueue Jobs regularly according to a cron schedule - daily, weekly, hourly, or any custom interval. This is ideal for tasks or workflows that must repeat automatically and without manual intervention.
Scheduling Jobs differs significantly from running them:
- **Queueing**: Scheduling only creates (enqueues) the Job according to your cron expression. It does not immediately execute any business logic.
- **Running**: Execution happens separately through your Jobs runner - such as autorun, or manual invocation using `payload.jobs.run()` or the `payload-jobs/run` endpoint.
Use the `schedule` property specifically when you have recurring tasks or workflows. To enqueue a single Job to run once in the future, use the `waitUntil` property instead.
## Example use cases
**Regular emails or notifications**
Send nightly digests, weekly newsletters, or hourly updates.
**Batch processing during off-hours**
Process analytics data or rebuild static sites during low-traffic times.
**Periodic data synchronization**
Regularly push or pull updates to or from external APIs.
## Handling schedules
Something needs to actually trigger the scheduling of jobs (execute the scheduling lifecycle seen below). By default, the `jobs.autorun` configuration, as well as the `/api/payload-jobs/run` will also handle scheduling for the queue specified in the `autorun` configuration.
You can disable this behavior by setting `disableScheduling: true` in your `autorun` configuration, or by passing `disableScheduling=true` to the `/api/payload-jobs/run` endpoint. This is useful if you want to handle scheduling manually, for example, by using a cron job or a serverless function that calls the `/api/payload-jobs/handle-schedules` endpoint or the `payload.jobs.handleSchedules()` local API method.
## Defining schedules on Tasks or Workflows
Schedules are defined using the `schedule` property:
```ts
export type ScheduleConfig = {
cron: string // required, supports seconds precision
queue: string // required, the queue to push Jobs onto
hooks?: {
// Optional hooks to customize scheduling behavior
beforeSchedule?: BeforeScheduleFn
afterSchedule?: AfterScheduleFn
}
}
```
### Example schedule
The following example demonstrates scheduling a Job to enqueue every day at midnight:
```ts
import type { TaskConfig } from 'payload'
export const SendDigestEmail: TaskConfig<'SendDigestEmail'> = {
slug: 'SendDigestEmail',
schedule: [
{
cron: '0 0 * * *', // Every day at midnight
queue: 'nightly',
},
],
handler: async () => {
await sendDigestToAllUsers()
},
}
```
This configuration only queues the Job - it does not execute it immediately. To actually run the queued Job, you configure autorun in your Payload config (note that autorun should **not** be used on serverless platforms):
```ts
export default buildConfig({
jobs: {
autoRun: [
{
cron: '* * * * *', // Runs every minute
queue: 'nightly',
},
],
tasks: [SendDigestEmail],
},
})
```
That way, Payload's scheduler will automatically enqueue the job into the `nightly` queue every day at midnight. The autorun configuration will check the `nightly` queue every minute and execute any Jobs that are due to run.
## Scheduling lifecycle
Here's how the scheduling process operates in detail:
1. **Cron evaluation**: Payload (or your external trigger in `manual` mode) identifies which schedules are due to run. To do that, it will
read the `payload-jobs-stats` global which contains information about the last time each scheduled task or workflow was run.
2. **BeforeSchedule hook**:
- The default beforeSchedule hook checks how many active or runnable jobs of the same type that have been queued by the scheduling system currently exist.
If such a job exists, it will skip scheduling a new one.
- You can provide your own `beforeSchedule` hook to customize this behavior. For example, you might want to allow multiple overlapping Jobs or dynamically set the Job input data.
3. **Enqueue Job**: Payload queues up a new job. This job will have `waitUntil` set to the next scheduled time based on the cron expression.
4. **AfterSchedule hook**:
- The default afterSchedule hook updates the `payload-jobs-stats` global metadata with the last scheduled time for the Job.
- You can provide your own afterSchedule hook to it for custom logging, metrics, or other post-scheduling actions.
## Customizing concurrency and input (Advanced)
You may want more control over concurrency or dynamically set Job inputs at scheduling time. For instance, allowing multiple overlapping Jobs to be scheduled, even if a previously scheduled job has not completed yet, or preparing dynamic data to pass to your Job handler:
```ts
import { countRunnableOrActiveJobsForQueue } from 'payload'
schedule: [
{
cron: '* * * * *', // every minute
queue: 'reports',
hooks: {
beforeSchedule: async ({ queueable, req }) => {
const runnableOrActiveJobsForQueue =
await countRunnableOrActiveJobsForQueue({
queue: queueable.scheduleConfig.queue,
req,
taskSlug: queueable.taskConfig?.slug,
workflowSlug: queueable.workflowConfig?.slug,
onlyScheduled: true,
})
// Allow up to 3 simultaneous scheduled jobs and set dynamic input
return {
shouldSchedule: runnableOrActiveJobsForQueue < 3,
input: { text: 'Hi there' },
}
},
},
},
]
```
This allows fine-grained control over how many Jobs can run simultaneously and provides dynamically computed input values each time a Job is scheduled.
## Scheduling in serverless environments
On serverless platforms, scheduling must be triggered externally since Payload does not automatically run cron schedules in ephemeral environments. You have two main ways to trigger scheduling manually:
- **Invoke via Payload's API:** `payload.jobs.handleSchedules()`
- **Use the REST API endpoint:** `/api/payload-jobs/handle-schedules`
- **Use the run endpoint, which also handles scheduling by default:** `GET /api/payload-jobs/run`
For example, on Vercel, you can set up a Vercel Cron to regularly trigger scheduling:
- **Vercel Cron Job:** Configure Vercel Cron to periodically call `GET /api/payload-jobs/handle-schedules`. If you would like to auto-run your scheduled jobs as well, you can use the `GET /api/payload-jobs/run` endpoint.
Once Jobs are queued, their execution depends entirely on your configured runner setup (e.g., autorun, or manual invocation).

View File

@@ -45,13 +45,11 @@ The following options are available:
| Path | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`url`** \* | String, or function that returns a string, pointing to your front-end application. This value is used as the iframe `src`. [More details](#url). |
| **`url`** | String, or function that returns a string, pointing to your front-end application. This value is used as the iframe `src`. [More details](#url). |
| **`breakpoints`** | Array of breakpoints to be used as “device sizes” in the preview window. Each item appears as an option in the toolbar. [More details](#breakpoints). |
| **`collections`** | Array of collection slugs to enable Live Preview on. |
| **`globals`** | Array of global slugs to enable Live Preview on. |
_\* An asterisk denotes that a property is required._
### URL
The `url` property resolves to a string that points to your front-end application. This value is used as the `src` attribute of the iframe rendering your front-end. Once loaded, the Admin Panel will communicate directly with your app through `window.postMessage` events.
@@ -88,17 +86,16 @@ const config = buildConfig({
// ...
livePreview: {
// highlight-start
url: ({
data,
collectionConfig,
locale
}) => `${data.tenant.url}${ // Multi-tenant top-level domain
collectionConfig.slug === 'posts' ? `/posts/${data.slug}` : `${data.slug !== 'home' : `/${data.slug}` : ''}`
}${locale ? `?locale=${locale?.code}` : ''}`, // Localization query param
url: ({ data, collectionConfig, locale }) =>
`${data.tenant.url}${
collectionConfig.slug === 'posts'
? `/posts/${data.slug}`
: `${data.slug !== 'home' ? `/${data.slug}` : ''}`
}${locale ? `?locale=${locale?.code}` : ''}`, // Localization query param
collections: ['pages'],
},
// highlight-end
}
},
})
```

View File

@@ -51,6 +51,7 @@ export default async function Page() {
collection: 'pages',
id: '123',
draft: true,
trash: true, // add this if trash is enabled in your collection and want to preview trashed documents
})
return (

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
@@ -194,6 +199,27 @@ const result = await payload.count({
})
```
### FindDistinct#collection-find-distinct
```js
// Result will be an object with:
// {
// values: ['value-1', 'value-2'], // array of distinct values,
// field: 'title', // the field
// totalDocs: 10, // count of the distinct values satisfies query,
// perPage: 10, // count of distinct values per page (based on provided limit)
// }
const result = await payload.findDistinct({
collection: 'posts', // required
locale: 'en',
where: {}, // pass a `where` query here
user: dummyUser,
overrideAccess: false,
field: 'title',
sort: 'title',
})
```
### Update by ID#collection-update-by-id
```js

View File

@@ -393,7 +393,7 @@ export default function LoginForm() {
### Logout
Logs out the current user by clearing the authentication cookie.
Logs out the current user by clearing the authentication cookie and current sessions.
#### Importing the `logout` function
@@ -401,7 +401,7 @@ Logs out the current user by clearing the authentication cookie.
import { logout } from '@payloadcms/next/auth'
```
Similar to the login function, you now need to pass your Payload config to this function and this cannot be done in a client component. Use a helper server function as shown below.
Similar to the login function, you now need to pass your Payload config to this function and this cannot be done in a client component. Use a helper server function as shown below. To ensure all sessions are cleared, set `allSessions: true` in the options, if you wish to logout but keep current sessions active, you can set this to `false` or leave it `undefined`.
```ts
'use server'
@@ -411,7 +411,7 @@ import config from '@payload-config'
export async function logoutAction() {
try {
return await logout({ config })
return await logout({ allSessions: true, config })
} catch (error) {
throw new Error(
`Logout failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
@@ -434,7 +434,7 @@ export default function LogoutButton() {
### Refresh
Refreshes the authentication token for the logged-in user.
Refreshes the authentication token and current session for the logged-in user.
#### Importing the `refresh` function
@@ -453,7 +453,6 @@ import config from '@payload-config'
export async function refreshAction() {
try {
return await refresh({
collection: 'users', // pass your collection slug
config,
})
} catch (error) {

View File

@@ -0,0 +1,244 @@
---
title: Performance
label: Overview
order: 10
desc: Ensure your Payload app runs as quickly and efficiently as possible.
keywords: performance, optimization, indexes, depth, select, block references, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Payload is designed with performance in mind, but its customizability means that there are many ways to configure your app that can impact performance.
With this in mind, Payload provides several options and best practices to help you optimize your app's specific performance needs. This includes the database, APIs, and Admin Panel.
Whether you're building an app or troubleshooting an existing one, follow these guidelines to ensure that it runs as quickly and efficiently as possible.
## Building your application
### Database proximity
The proximity of your database to your server can significantly impact performance. Ensure that your database is hosted in the same region as your server to minimize latency and improve response times.
### Indexing your fields
If a particular field is queried often, build an [Index](../database/indexes) for that field to produce faster queries.
When your query runs, the database will not search the entire document to find that one field, but will instead use the index to quickly locate the data.
To learn more, see the [Indexes](../database/indexes) docs.
### Querying your data
There are several ways to optimize your [Queries](../queries/overview). Many of these options directly impact overall database overhead, response sizes, and/or computational load and can significantly improve performance.
When building queries, combine as many of these options together as possible. This will ensure your queries are as efficient as they can be.
To learn more, see the [Query Performance](../queries/overview#performance) docs.
### Optimizing your APIs
When querying data through Payload APIs, the request lifecycle includes running hooks, access control, validations, and other operations that can add significant overhead to the request.
To optimize your APIs, any custom logic should be as efficient as possible. This includes writing lightweight hooks, preventing memory leaks, offloading long-running tasks, and optimizing custom validations.
To learn more, see the [Hooks Performance](../hooks/overview#performance) docs.
### Writing efficient validations
If your validation functions are asynchronous or computationally heavy, ensure they only run when necessary.
To learn more, see the [Validation Performance](../fields/overview#validation-performance) docs.
### Optimizing custom components
When building custom components in the Admin Panel, ensure that they are as efficient as possible. This includes using React best practices such as memoization, lazy loading, and avoiding unnecessary re-renders.
To learn more, see the [Custom Components Performance](../admin/custom-components#performance) docs.
## Other Best Practices
### Block references
Use [Block References](../fields/blocks#block-references) to share the same block across multiple fields without bloating the config. This will reduce the number of fields to traverse when processing permissions, etc. and can significantly reduce the amount of data sent from the server to the client in the Admin Panel.
For example, if you have a block that is used in multiple fields, you can define it once and reference it in each field.
To do this, use the `blockReferences` option in your blocks field:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
blocks: [
{
slug: 'TextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
collections: [
{
slug: 'posts',
fields: [
{
name: 'content',
type: 'blocks',
// highlight-start
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty, for compatibility reasons
// highlight-end
},
],
},
{
slug: 'pages',
fields: [
{
name: 'content',
type: 'blocks',
// highlight-start
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty, for compatibility reasons
// highlight-end
},
],
},
],
})
```
### Using the cached Payload instance
Ensure that you do not instantiate Payload unnecessarily. Instead, Payload provides a caching mechanism to reuse the same instance across your app.
To do this, use the `getPayload` function to get the cached instance of Payload:
```ts
import { getPayload } from 'payload'
import config from '@payload-config'
const myFunction = async () => {
const payload = await getPayload({ config })
// use payload here
}
```
### When to make direct-to-db calls
<Banner type="warning">
**Warning:** Direct database calls bypass all hooks and validations. Only use
this method when you are certain that the operation is safe and does not
require any of these features.
</Banner>
Making direct database calls can significantly improve performance by bypassing much of the request lifecycle such as hooks, validations, and other overhead associated with Payload APIs.
For example, this can be especially useful for the `update` operation, where Payload would otherwise need to make multiple API calls to fetch, update, and fetch again. Making a direct database call can reduce this to a single operation.
To do this, use the `payload.db` methods:
```ts
await payload.db.updateOne({
collection: 'posts',
id: post.id,
data: {
title: 'New Title',
},
})
```
<Banner type="warning">
**Note:** Direct database methods do not start a
[transaction](../database/transactions). You have to start that yourself.
</Banner>
#### Returning
To prevent unnecessary database computation and reduce the size of the response, you can also set `returning: false` in your direct database calls if you don't need the updated document returned to you.
```ts
await payload.db.updateOne({
collection: 'posts',
id: post.id,
data: { title: 'New Title' }, // See note above ^ about Postgres
// highlight-start
returning: false,
// highlight-end
})
```
<Banner type="warning">
**Note:** The `returning` option is only available on direct-to-db methods.
E.g. those on the `payload.db` object. It is not exposed to the Local API.
</Banner>
### Avoid bundling the entire UI library in your front-end
If your front-end imports from `@payloadcms/ui`, ensure that you do not bundle the entire package as this can significantly increase your bundle size.
To do this, import using the full path to the specific component you need:
```ts
import { Button } from '@payloadcms/ui/elements/Button'
```
Custom components within the Admin Panel, however, do not have this same restriction and can import directly from `@payloadcms/ui`:
```ts
import { Button } from '@payloadcms/ui'
```
<Banner type="success">
**Tip:** Use
[`@next/bundle-analyzer`](https://nextjs.org/docs/app/guides/package-bundling)
to analyze your component tree and identify unnecessary re-renders or large
components that could be optimized.
</Banner>
## Optimizing local development
Everything mentioned above applies to local development as well, but there are a few additional steps you can take to optimize your local development experience.
### Enable Turbopack
<Banner type="warning">
**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.
```json
{
"scripts": {
"dev": "next dev --turbo"
}
}
```
### Only bundle server packages in production
<Banner type="warning">
**Note:** This is enabled by default in `create-payload-app` since v3.28.0. If
you created your app after this version, you don't need to do anything.
</Banner>
By default, Next.js bundles both server and client code. However, during development, bundling certain server packages isn't necessary.
Payload has thousands of modules, slowing down compilation.
Setting this option skips bundling Payload server modules during development. Fewer files to compile means faster compilation speeds.
To do this, add the `devBundleServerPackages` option to `withPayload` in your `next.config.js` file:
```ts
const nextConfig = {
// your existing next config
}
export default withPayload(nextConfig, { devBundleServerPackages: false })
```

View File

@@ -1,7 +1,7 @@
---
title: Form Builder Plugin
label: Form Builder
order: 40
order: 30
desc: Easily build and manage forms from the Admin Panel. Send dynamic, personalized emails and even accept and process payments.
keywords: plugins, plugin, form, forms, form builder
---
@@ -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

@@ -0,0 +1,155 @@
---
title: Import Export Plugin
label: Import Export
order: 40
desc: Add Import and export functionality to create CSV and JSON data exports
keywords: plugins, plugin, import, export, csv, JSON, data, ETL, download
---
![https://www.npmjs.com/package/@payloadcms/plugin-import-export](https://img.shields.io/npm/v/@payloadcms/plugin-import-export)
<Banner type="warning">
**Note**: This plugin is in **beta** as some aspects of it may change on any
minor releases. It is under development and currently only supports exporting
of collection data.
</Banner>
This plugin adds features that give admin users the ability to download or create export data as an upload collection and import it back into a project.
## Core Features
- Export data as CSV or JSON format via the admin UI
- Download the export directly through the browser
- Create a file upload of the export data
- Use the jobs queue for large exports
- (Coming soon) Import collection data
## Installation
Install the plugin using any JavaScript package manager like [pnpm](https://pnpm.io), [npm](https://npmjs.com), or [Yarn](https://yarnpkg.com):
```bash
pnpm add @payloadcms/plugin-import-export
```
## Basic Usage
In the `plugins` array of your [Payload Config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
```ts
import { buildConfig } from 'payload'
import { importExportPlugin } from '@payloadcms/plugin-import-export'
const config = buildConfig({
collections: [Pages, Media],
plugins: [
importExportPlugin({
collections: ['users', 'pages'],
// see below for a list of available options
}),
],
})
export default config
```
## Options
| Property | Type | Description |
| -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `collections` | string[] | Collections to include Import/Export controls in. Defaults to all collections. |
| `debug` | boolean | If true, enables debug logging. |
| `disableDownload` | boolean | If true, disables the download button in the export preview UI. |
| `disableJobsQueue` | boolean | If true, forces the export to run synchronously. |
| `disableSave` | boolean | If true, disables the save button in the export preview UI. |
| `format` | string | Forces a specific export format (`csv` or `json`), hides the format dropdown, and prevents the user from choosing the export format. |
| `overrideExportCollection` | function | Function to override the default export collection; takes the default export collection and allows you to modify and return it. |
## Field Options
In addition to the above plugin configuration options, you can granularly set the following field level options using the `custom['plugin-import-export']` properties in any of your collections.
| Property | Type | Description |
| ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `disabled` | boolean | When `true` the field is completely excluded from the import-export plugin. |
| `toCSV` | function | Custom function used to modify the outgoing csv data by manipulating the data, siblingData or by returning the desired value. |
### Customizing the output of CSV data
To manipulate the data that a field exports you can add `toCSV` custom functions. This allows you to modify the outgoing csv data by manipulating the data, siblingData or by returning the desired value.
The toCSV function argument is an object with the following properties:
| Property | Type | Description |
| ------------ | ------- | ----------------------------------------------------------------- |
| `columnName` | string | The CSV column name given to the field. |
| `doc` | object | The top level document |
| `row` | object | The object data that can be manipulated to assign data to the CSV |
| `siblingDoc` | object | The document data at the level where it belongs |
| `value` | unknown | The data for the field. |
Example function:
```ts
const pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'author',
type: 'relationship',
relationTo: 'users',
custom: {
'plugin-import-export': {
toCSV: ({ value, columnName, row }) => {
// add both `author_id` and the `author_email` to the csv export
if (
value &&
typeof value === 'object' &&
'id' in value &&
'email' in value
) {
row[`${columnName}_id`] = (value as { id: number | string }).id
row[`${columnName}_email`] = (value as { email: string }).email
}
},
},
},
},
],
}
```
## Exporting Data
There are four possible ways that the plugin allows for exporting documents, the first two are available in the admin UI from the list view of a collection:
1. Direct download - Using a `POST` to `/api/exports/download` and streams the response as a file download
2. File storage - Goes to the `exports` collection as an uploads enabled collection
3. Local API - A create call to the uploads collection: `payload.create({ slug: 'uploads', ...parameters })`
4. Jobs Queue - `payload.jobs.queue({ task: 'createCollectionExport', input: parameters })`
By default, a user can use the Export drawer to create a file download by choosing `Save` or stream a downloadable file directly without persisting it by using the `Download` button. Either option can be disabled to provide the export experience you desire for your use-case.
The UI for creating exports provides options so that users can be selective about which documents to include and also which columns or fields to include.
It is necessary to add access control to the uploads collection configuration using the `overrideExportCollection` function if you have enabled this plugin on collections with data that some authenticated users should not have access to.
<Banner type="warning">
**Note**: Users who have read access to the upload collection may be able to
download data that is normally not readable due to [access
control](../access-control/overview).
</Banner>
The following parameters are used by the export function to handle requests:
| Property | Type | Description |
| ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `format` | text | Either `csv` or `json` to determine the shape of data exported |
| `limit` | number | The max number of documents to return |
| `sort` | select | The field to use for ordering documents |
| `locale` | string | The locale code to query documents or `all` |
| `draft` | string | Either `yes` or `no` to return documents with their newest drafts for drafts enabled collections |
| `fields` | string[] | Which collection fields are used to create the export, defaults to all |
| `collectionSlug` | string | The slug to query against |
| `where` | object | The WhereObject used to query documents to export. This is set by making selections or filters from the list view |
| `filename` | text | What to call the export being created |

View File

@@ -1,7 +1,7 @@
---
title: Multi-Tenant Plugin
label: Multi-Tenant
order: 40
order: 50
desc: Scaffolds multi-tenancy for your Payload application
keywords: plugins, multi-tenant, multi-tenancy, plugin, payload, cms, seo, indexing, search, search engine
---
@@ -53,6 +53,14 @@ The plugin accepts an object with the following properties:
```ts
type MultiTenantPluginConfig<ConfigTypes = unknown> = {
/**
* Base path for your application
*
* https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath
*
* @default undefined
*/
basePath?: string
/**
* After a tenant is deleted, the plugin will attempt to clean up related documents
* - removing documents with the tenant ID
@@ -72,8 +80,30 @@ 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
*/
tenantFieldOverrides?: CollectionTenantFieldConfigOverrides
/**
* Set to `false` if you want to manually apply the baseListFilter
* Set to `false` if you want to manually apply the baseFilter
*
* @default true
*/
useBaseFilter?: boolean
/**
* @deprecated Use `useBaseFilter` instead. If both are defined,
* `useBaseFilter` will take precedence. This property remains only
* for backward compatibility and may be removed in a future version.
*
* Originally, `baseListFilter` was intended to filter only the List View
* in the admin panel. However, base filtering is often required in other areas
* such as internal link relationships in the Lexical editor.
*
* @default true
*/
@@ -99,18 +129,37 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
* @default true
*/
enabled?: boolean
/**
* Localization for the plugin
*/
i18n?: {
translations: {
[key in AcceptedLanguages]?: {
/**
* @default 'You are about to change ownership from <0>{{fromTenant}}</0> to <0>{{toTenant}}</0>'
*/
'confirm-modal-tenant-switch--body'?: string
/**
* `tenantLabel` defaults to the value of the `nav-tenantSelector-label` translation
*
* @default 'Confirm {{tenantLabel}} change'
*/
'confirm-modal-tenant-switch--heading'?: string
/**
* @default 'Assigned Tenant'
*/
'field-assignedTenant-label'?: string
/**
* @default 'Tenant'
*/
'nav-tenantSelector-label'?: string
}
}
}
/**
* Field configuration for the field added to all tenant enabled collections
*/
tenantField?: {
access?: RelationshipField['access']
/**
* The name of the field added to all tenant enabled collections
*
* @default 'tenant'
*/
name?: string
}
tenantField?: RootTenantFieldConfigOverrides
/**
* Field configuration for the field added to the users collection
*
@@ -163,6 +212,8 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
* Customize tenant selector label
*
* Either a string or an object where the keys are i18n codes and the values are the string labels
*
* @deprecated Use `i18n.translations` instead.
*/
tenantSelectorLabel?:
| Partial<{
@@ -181,7 +232,9 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
* Useful for super-admin type users
*/
userHasAccessToAllTenants?: (
user: ConfigTypes extends { user: unknown } ? ConfigTypes['user'] : User,
user: ConfigTypes extends { user: unknown }
? ConfigTypes['user']
: TypedUser,
) => boolean
/**
* Opt out of adding access constraints to the tenants collection
@@ -212,15 +265,15 @@ const config = buildConfig({
{
slug: 'tenants',
admin: {
useAsTitle: 'name'
}
useAsTitle: 'name',
},
fields: [
// remember, you own these fields
// these are merely suggestions/examples
{
name: 'name',
type: 'text',
required: true,
name: 'name',
type: 'text',
required: true,
},
{
name: 'slug',
@@ -231,7 +284,7 @@ const config = buildConfig({
name: 'domain',
type: 'text',
required: true,
}
},
],
},
],
@@ -241,7 +294,7 @@ const config = buildConfig({
pages: {},
navigation: {
isGlobal: true,
}
},
},
}),
],
@@ -327,14 +380,16 @@ type ContextType = {
/**
* Prevents a refresh when the tenant is changed
*
* If not switching tenants while viewing a "global", set to true
* If not switching tenants while viewing a "global",
* set to true
*/
setPreventRefreshOnChange: React.Dispatch<React.SetStateAction<boolean>>
/**
* Sets the selected tenant ID
*
* @param args.id - The ID of the tenant to select
* @param args.refresh - Whether to refresh the page after changing the tenant
* @param args.refresh - Whether to refresh the page
* after changing the tenant
*/
setTenant: (args: {
id: number | string | undefined

View File

@@ -1,7 +1,7 @@
---
title: Nested Docs Plugin
label: Nested Docs
order: 40
order: 60
desc: Nested documents in a parent, child, and sibling relationship.
keywords: plugins, nested, documents, parent, child, sibling, relationship
---

View File

@@ -55,6 +55,7 @@ Payload maintains a set of Official Plugins that solve for some of the common us
- [Sentry](./sentry)
- [SEO](./seo)
- [Stripe](./stripe)
- [Import/Export](./import-export)
You can also [build your own plugin](./build-your-own) to easily extend Payload's functionality in some other way. Once your plugin is ready, consider [sharing it with the community](#community-plugins).

View File

@@ -1,7 +1,7 @@
---
title: Redirects Plugin
label: Redirects
order: 40
order: 70
desc: Automatically create redirects for your Payload application
keywords: plugins, redirects, redirect, plugin, payload, cms, seo, indexing, search, search engine
---

View File

@@ -1,7 +1,7 @@
---
title: Search Plugin
label: Search
order: 40
order: 80
desc: Generates records of your documents that are extremely fast to search on.
keywords: plugins, search, search plugin, search engine, search index, search results, search bar, search box, search field, search form, search input
---

View File

@@ -1,7 +1,7 @@
---
title: Sentry Plugin
label: Sentry
order: 40
order: 90
desc: Integrate Sentry error tracking into your Payload application
keywords: plugins, sentry, error, tracking, monitoring, logging, bug, reporting, performance
---
@@ -74,16 +74,32 @@ import * as Sentry from '@sentry/nextjs'
const config = buildConfig({
collections: [Pages, Media],
plugins: [
sentryPlugin({
Sentry,
}),
],
plugins: [sentryPlugin({ Sentry })],
})
export default config
```
## Instrumenting Database Queries
If you want Sentry to capture Postgres query performance traces, you need to inject the Sentry-patched `pg` driver into the Postgres adapter. This ensures Sentrys instrumentation hooks into your database calls.
```ts
import * as Sentry from '@sentry/nextjs'
import { buildConfig } from 'payload'
import { sentryPlugin } from '@payloadcms/plugin-sentry'
import { postgresAdapter } from '@payloadcms/db-postgres'
import pg from 'pg'
export default buildConfig({
db: postgresAdapter({
pool: { connectionString: process.env.DATABASE_URL },
pg, // Inject the patched pg driver for Sentry instrumentation
}),
plugins: [sentryPlugin({ Sentry })],
})
```
## Options
- `Sentry` : Sentry | **required**

View File

@@ -2,7 +2,7 @@
description: Manage SEO metadata from your Payload admin
keywords: plugins, seo, meta, search, engine, ranking, google
label: SEO
order: 30
order: 100
title: SEO Plugin
---

View File

@@ -1,7 +1,7 @@
---
title: Stripe Plugin
label: Stripe
order: 40
order: 110
desc: Easily accept payments with Stripe
keywords: plugins, stripe, payments, ecommerce
---

View File

@@ -14,7 +14,9 @@ Solutions:
## Using the experimental-build-mode Next.js build flag
You can run Next.js build using the `pnpx next build --experimental-build-mode compile` command to only compile the code without static generation, which does not require a DB connection. In that case, your pages will be rendered dynamically, but after that, you can still generate static pages using the `pnpx next build --experimental-build-mode generate` command when you have a DB connection.
You can run Next.js build using the `pnpm next build --experimental-build-mode compile` command to only compile the code without static generation, which does not require a DB connection. In that case, your pages will be rendered dynamically, but after that, you can still generate static pages using the `pnpm next build --experimental-build-mode generate` command when you have a DB connection.
When running `pnpm next build --experimental-build-mode compile`, environment variables prefixed with `NEXT_PUBLIC` will not be inlined and will be `undefined` on the client. To make these variables available, either run `pnpm next build --experimental-build-mode generate` if a DB connection is available, or use `pnpm next build --experimental-build-mode generate-env` if you do not have a DB connection.
[Next.js documentation](https://nextjs.org/docs/pages/api-reference/cli/next#next-build-options)

View File

@@ -24,16 +24,6 @@ Payload can be deployed _anywhere that Next.js can run_ - including Vercel, Netl
But it's important to remember that most Payload projects will also need a database, file storage, an email provider, and a CDN. Make sure you have all of the requirements that your project needs, no matter what deployment platform you choose.
Often, the easiest and fastest way to deploy Payload is to use [Payload Cloud](https://payloadcms.com/new) — where you get everything you need out of the box, including:
1. A MongoDB Atlas database
1. S3 file storage
1. Resend email service
1. Cloudflare CDN
1. Blue / green deployments
1. Logs
1. And more
## Basics
Payload runs fully in Next.js, so the [Next.js build process](https://nextjs.org/docs/app/building-your-application/deploying) is used for building Payload. If you've used `create-payload-app` to create your project, executing the `build`

View File

@@ -8,7 +8,7 @@ keywords: query, documents, pagination, documentation, Content Management System
Documents in Payload can have relationships to other Documents. This is true for both [Collections](../configuration/collections) as well as [Globals](../configuration/globals). When you query a Document, you can specify the depth at which to populate any of its related Documents either as full objects, or only their IDs.
Depth will optimize the performance of your application by limiting the amount of processing made in the database and significantly reducing the amount of data returned. Since Documents can be infinitely nested or recursively related, it's important to be able to control how deep your API populates.
Since Documents can be infinitely nested or recursively related, it's important to be able to control how deep your API populates. Depth can impact the performance of your queries by affecting the load on the database and the size of the response.
For example, when you specify a `depth` of `0`, the API response might look like this:
@@ -48,7 +48,9 @@ import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
depth: 2, // highlight-line
// highlight-start
depth: 2,
// highlight-end
})
return posts
@@ -65,7 +67,9 @@ const getPosts = async (payload: Payload) => {
To specify depth in the [REST API](../rest-api/overview), you can use the `depth` parameter in your query:
```ts
fetch('https://localhost:3000/api/posts?depth=2') // highlight-line
// highlight-start
fetch('https://localhost:3000/api/posts?depth=2')
// highlight-end
.then((res) => res.json())
.then((data) => console.log(data))
```
@@ -75,6 +79,24 @@ fetch('https://localhost:3000/api/posts?depth=2') // highlight-line
the `/api/globals` endpoint.
</Banner>
## Default Depth
If no depth is specified in the request, Payload will use its default depth for all requests. By default, this is set to `2`.
To change the default depth on the application level, you can use the `defaultDepth` option in your root Payload config:
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
// ...
// highlight-start
defaultDepth: 1,
// highlight-end
// ...
})
```
## Max Depth
Fields like the [Relationship Field](../fields/relationship) or the [Upload Field](../fields/upload) can also set a maximum depth. If exceeded, this will limit the population depth regardless of what the depth might be on the request.
@@ -89,7 +111,9 @@ To set a max depth for a field, use the `maxDepth` property in your field config
name: 'author',
type: 'relationship',
relationTo: 'users',
maxDepth: 2, // highlight-line
// highlight-start
maxDepth: 2,
// highlight-end
}
]
}

View File

@@ -60,7 +60,7 @@ The following operators are available for use in queries:
<Banner type="success">
**Tip:** If you know your users will be querying on certain fields a lot, add
`index: true` to the Field Config. This will speed up searches using that
field immensely.
field immensely. [More details](../database/indexes).
</Banner>
### And / Or Logic
@@ -192,3 +192,130 @@ const getPosts = async () => {
// Continue to handle the response below...
}
```
## Performance
There are several ways to optimize your queries. Many of these options directly impact overall database overhead, response sizes, and/or computational load and can significantly improve performance.
When building queries, combine as many of these strategies together as possible to ensure your queries are as performant as they can be.
<Banner type="success">
For more performance tips, see the [Performance
documentation](../performance/overview).
</Banner>
### Indexes
Build [Indexes](../database/indexes) for fields that are often queried or sorted by.
When your query runs, the database will not search the entire document to find that one field, but will instead use the index to quickly locate the data.
This is done by adding `index: true` to the Field Config for that field:
```ts
// In your collection configuration
{
name: 'posts',
fields: [
{
name: 'title',
type: 'text',
// highlight-start
index: true, // Add an index to the title field
// highlight-end
},
// Other fields...
],
}
```
To learn more, see the [Indexes documentation](../database/indexes).
### Depth
Set the [Depth](./depth) to only the level that you need to avoid populating unnecessary related documents.
Relationships will only populate down to the specified depth, and any relationships beyond that depth will only return the ID of the related document.
```ts
const posts = await payload.find({
collection: 'posts',
where: { ... },
// highlight-start
depth: 0, // Only return the IDs of related documents
// highlight-end
})
```
To learn more, see the [Depth documentation](./depth).
### Limit
Set the [Limit](./pagination#limit) if you can reliably predict the number of matched documents, such as when querying on a unique field.
```ts
const posts = await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'unique-post-slug',
},
},
// highlight-start
limit: 1, // Only expect one document to be returned
// highlight-end
})
```
<Banner type="success">
**Tip:** Use in combination with `pagination: false` for best performance when
querying by unique fields.
</Banner>
To learn more, see the [Limit documentation](./pagination#limit).
### Select
Use the [Select API](./select) to only process and return the fields you need.
This will reduce the amount of data returned from the request, and also skip processing of any fields that are not selected, such as running their field hooks.
```ts
const posts = await payload.find({
collection: 'posts',
where: { ... },
// highlight-start
select: [{
title: true,
}],
// highlight-end
```
This is a basic example, but there are many ways to use the Select API, including selecting specific fields, excluding fields, etc.
To learn more, see the [Select documentation](./select).
### Pagination
[Disable Pagination](./pagination#disabling-pagination) if you can reliably predict the number of matched documents, such as when querying on a unique field.
```ts
const posts = await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'unique-post-slug',
},
},
// highlight-start
pagination: false, // Return all matched documents without pagination
// highlight-end
})
```
<Banner type="success">
**Tip:** Use in combination with `limit: 1` for best performance when querying
by unique fields.
</Banner>
To learn more, see the [Pagination documentation](./pagination).

View File

@@ -6,9 +6,61 @@ desc: Payload queries are equipped with automatic pagination so you create pagin
keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
All collection `find` queries are paginated automatically. Responses are returned with top-level meta data related to pagination, and returned documents are nested within a `docs` array.
With Pagination you can limit the number of documents returned per page, and get a specific page of results. This is useful for creating paginated lists of documents within your application.
**`Find` response properties:**
All paginated responses include documents nested within a `docs` array, and return top-level meta data related to pagination such as `totalDocs`, `limit`, `totalPages`, `page`, and more.
<Banner type="success">
**Note:** Collection `find` queries are paginated automatically.
</Banner>
## Options
All Payload APIs support the pagination controls below. With them, you can create paginated lists of documents within your application:
| Control | Default | Description |
| ------------ | ------- | ------------------------------------------------------------------------- |
| `limit` | `10` | Limits the number of documents returned per page. [More details](#limit). |
| `pagination` | `true` | Set to `false` to disable pagination and return all documents. |
| `page` | `1` | Get a specific page number. |
## Local API
To specify pagination controls in the [Local API](../local-api/overview), you can use the `limit`, `page`, and `pagination` options in your query:
```ts
import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
// highlight-start
limit: 10,
page: 2,
// highlight-end
})
return posts
}
```
## REST API
With the [REST API](../rest-api/overview), you can use the pagination controls below as query strings:
```ts
// highlight-start
fetch('https://localhost:3000/api/posts?limit=10&page=2')
// highlight-end
.then((res) => res.json())
.then((data) => console.log(data))
```
## Response
All paginated responses include documents nested within a `docs` array, and return top-level meta data related to pagination.
The `find` operation includes the following properties in its response:
| Property | Description |
| --------------- | --------------------------------------------------------- |
@@ -51,16 +103,59 @@ All collection `find` queries are paginated automatically. Responses are returne
}
```
## Pagination controls
## Limit
All Payload APIs support the pagination controls below. With them, you can create paginated lists of documents within your application:
You can specify a `limit` to restrict the number of documents returned per page.
| Control | Default | Description |
| ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limit` | `10` | Limits the number of documents returned per page - set to `0` to show all documents, we automatically disabled pagination for you when `limit` is `0` for optimisation |
| `pagination` | `true` | Set to `false` to disable pagination and return all documents |
| `page` | `1` | Get a specific page number |
<Banner type="warning">
**Reminder:** By default, any query with `limit: 0` will automatically
[disable pagination](#disabling-pagination).
</Banner>
### Disabling pagination within Local API
#### Performance benefits
If you are querying for a specific document and can reliably expect only one document to match, you can set a limit of `1` (or another low number) to reduce the number of database lookups and improve performance.
For example, when querying a document by a unique field such as `slug`, you can set the limit to `1` since you know there will only be one document with that slug.
To do this, set the `limit` option in your query:
```ts
await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'post-1',
},
},
// highlight-start
limit: 1,
// highlight-end
})
```
## Disabling pagination
Disabling pagination can improve performance by reducing the overhead of pagination calculations and improve query speed.
For `find` operations within the Local API, you can disable pagination to retrieve all documents from a collection by passing `pagination: false` to the `find` local operation.
To do this, set `pagination: false` in your query:
```ts
import type { Payload } from 'payload'
const getPost = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
where: {
title: { equals: 'My Post' },
},
// highlight-start
pagination: false,
// highlight-end
})
return posts
}
```

View File

@@ -6,9 +6,9 @@ desc: Payload select determines which fields are selected to the result.
keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
By default, Payload's APIs will return _all fields_ for a given collection or global. But, you may not need all of that data for all of your queries. Sometimes, you might want just a few fields from the response, which can speed up the Payload API and reduce the amount of JSON that is sent to you from the API.
By default, Payload's APIs will return _all fields_ for a given collection or global. But, you may not need all of that data for all of your queries. Sometimes, you might want just a few fields from the response.
This is where Payload's `select` feature comes in. Here, you can define exactly which fields you'd like to retrieve from the API.
With the Select API, you can define exactly which fields you'd like to retrieve. This can impact the performance of your queries by affecting the load on the database and the size of the response.
## Local API
@@ -21,6 +21,7 @@ import type { Payload } from 'payload'
const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
// highlight-start
select: {
text: true,
// select a specific field from group
@@ -29,7 +30,8 @@ const getPosts = async (payload: Payload) => {
},
// select all fields from array
array: true,
}, // highlight-line
},
// highlight-end
})
return posts
@@ -40,12 +42,14 @@ const getPosts = async (payload: Payload) => {
const posts = await payload.find({
collection: 'posts',
// Select everything except for array and group.number
// highlight-start
select: {
array: false,
group: {
number: false,
},
}, // highlight-line
},
// highlight-end
})
return posts
@@ -67,8 +71,10 @@ To specify select in the [REST API](../rest-api/overview), you can use the `sele
```ts
fetch(
// highlight-start
'https://localhost:3000/api/posts?select[color]=true&select[group][number]=true',
) // highlight-line
// highlight-end
)
.then((res) => res.json())
.then((data) => console.log(data))
```
@@ -142,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,
@@ -149,7 +161,7 @@ export const Pages: CollectionConfig<'pages'> = {
not be able to construct the correct file URL, instead returning `url: null`.
</Banner>
## populate
## Populate
Setting `defaultPopulate` will enforce that each time Payload performs a "population" of a related document, only the fields specified will be queried and returned. However, you can override `defaultPopulate` with the `populate` property in the Local and REST API:

View File

@@ -6,13 +6,15 @@ desc: Payload sort allows you to order your documents by a field in ascending or
keywords: query, documents, pagination, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order. If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order. In Local API multiple fields can be specified by using an array of strings. In REST API multiple fields can be specified by separating fields with comma. The minus symbol can be in front of individual fields.
Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order.
If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order. In Local API multiple fields can be specified by using an array of strings. In REST API multiple fields can be specified by separating fields with comma. The minus symbol can be in front of individual fields.
Because sorting is handled by the database, the field cannot be a [Virtual Field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) unless it's [linked with a relationship field](/docs/fields/relationship#linking-virtual-fields-with-relationships). It must be stored in the database to be searchable.
<Banner type="success">
**Tip:** For performance reasons, it is recommended to enable `index: true`
for the fields that will be sorted upon. [More details](../fields/overview).
for the fields that will be sorted upon. [More details](../database/indexes).
</Banner>
## Local API

View File

@@ -207,4 +207,4 @@ const config = buildConfig({
})
```
The `filterConstraints` function receives the same arguments as [`filterOptions`](../fields/select#filterOptions) in the [Select field](../fields/select).
The `filterConstraints` function receives the same arguments as [`filterOptions`](../fields/select#filteroptions) in the [Select field](../fields/select).

View File

@@ -474,11 +474,15 @@ const MyNodeComponent = React.lazy(() =>
)
/**
* This node is a DecoratorNode. DecoratorNodes allow you to render React components in the editor.
* This node is a DecoratorNode. DecoratorNodes allow
* you to render React components in the editor.
*
* They need both createDom and decorate functions. createDom => outside of the html. decorate => React Component inside of the html.
* They need both createDom and decorate functions.
* createDom => outside of the html.
* decorate => React Component inside of the html.
*
* If we used DecoratorBlockNode instead, we would only need a decorate method
* If we used DecoratorBlockNode instead,
* we would only need a decorate method
*/
export class MyNode extends DecoratorNode<React.ReactElement> {
static clone(node: MyNode): MyNode {
@@ -490,9 +494,11 @@ export class MyNode extends DecoratorNode<React.ReactElement> {
}
/**
* Defines what happens if you copy a div element from another page and paste it into the lexical editor
* Defines what happens if you copy a div element
* from another page and paste it into the lexical editor
*
* This also determines the behavior of lexical's internal HTML -> Lexical converter
* This also determines the behavior of lexical's
* internal HTML -> Lexical converter
*/
static importDOM(): DOMConversionMap | null {
return {
@@ -504,14 +510,18 @@ export class MyNode extends DecoratorNode<React.ReactElement> {
}
/**
* The data for this node is stored serialized as JSON. This is the "load function" of that node: it takes the saved data and converts it into a node.
* The data for this node is stored serialized as JSON.
* This is the "load function" of that node: it takes
* the saved data and converts it into a node.
*/
static importJSON(serializedNode: SerializedMyNode): MyNode {
return $createMyNode()
}
/**
* Determines how the hr element is rendered in the lexical editor. This is only the "initial" / "outer" HTML element.
* Determines how the hr element is rendered in the
* lexical editor. This is only the "initial" / "outer"
* HTML element.
*/
createDOM(config: EditorConfig): HTMLElement {
const element = document.createElement('div')
@@ -519,22 +529,28 @@ export class MyNode extends DecoratorNode<React.ReactElement> {
}
/**
* Allows you to render a React component within whatever createDOM returns.
* Allows you to render a React component within
* whatever createDOM returns.
*/
decorate(): React.ReactElement {
return <MyNodeComponent nodeKey={this.__key} />
}
/**
* Opposite of importDOM, this function defines what happens when you copy a div element from the lexical editor and paste it into another page.
* Opposite of importDOM, this function defines what
* happens when you copy a div element from the lexical
* editor and paste it into another page.
*
* This also determines the behavior of lexical's internal Lexical -> HTML converter
* This also determines the behavior of lexical's
* internal Lexical -> HTML converter
*/
exportDOM(): DOMExportOutput {
return { element: document.createElement('div') }
}
/**
* Opposite of importJSON. This determines what data is saved in the database / in the lexical editor state.
* Opposite of importJSON. This determines what
* data is saved in the database / in the lexical
* editor state.
*/
exportJSON(): SerializedLexicalNode {
return {
@@ -556,18 +572,23 @@ export class MyNode extends DecoratorNode<React.ReactElement> {
}
}
// This is used in the importDOM method. Totally optional if you do not want your node to be created automatically when copy & pasting certain dom elements
// into your editor.
// This is used in the importDOM method. Totally optional
// if you do not want your node to be created automatically
// when copy & pasting certain dom elements into your editor.
function $yourConversionMethod(): DOMConversionOutput {
return { node: $createMyNode() }
}
// This is a utility method to create a new MyNode. Utility methods prefixed with $ make it explicit that this should only be used within lexical
// This is a utility method to create a new MyNode.
// Utility methods prefixed with $ make it explicit
// that this should only be used within lexical
export function $createMyNode(): MyNode {
return $applyNodeReplacement(new MyNode())
}
// This is just a utility method you can use to check if a node is a MyNode. This also ensures correct typing.
// This is just a utility method you can use
// to check if a node is a MyNode. This also
// ensures correct typing.
export function $isMyNode(
node: LexicalNode | null | undefined,
): node is MyNode {
@@ -626,10 +647,12 @@ export const INSERT_MYNODE_COMMAND: LexicalCommand<void> = createCommand(
)
/**
* Plugin which registers a lexical command to insert a new MyNode into the editor
* Plugin which registers a lexical command to
* insert a new MyNode into the editor
*/
export const MyNodePlugin: PluginComponent = () => {
// The useLexicalComposerContext hook can be used to access the lexical editor instance
// The useLexicalComposerContext hook can be used
// to access the lexical editor instance
const [editor] = useLexicalComposerContext()
useEffect(() => {

View File

@@ -0,0 +1,494 @@
---
description: Features officially maintained by Payload.
keywords: lexical, rich text, editor, headless cms, official, features
label: Official Features
order: 35
title: Official Features
---
Below are all the Rich Text Features Payload offers. Everything is customizable; you can [create your own features](/docs/rich-text/custom-features), modify ours and share them with the community.
## Features Overview
| Feature Name | Included by default | Description |
| ------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`BoldFeature`** | Yes | Adds support for bold text formatting. |
| **`ItalicFeature`** | Yes | Adds support for italic text formatting. |
| **`UnderlineFeature`** | Yes | Adds support for underlined text formatting. |
| **`StrikethroughFeature`** | Yes | Adds support for strikethrough text formatting. |
| **`SubscriptFeature`** | Yes | Adds support for subscript text formatting. |
| **`SuperscriptFeature`** | Yes | Adds support for superscript text formatting. |
| **`InlineCodeFeature`** | Yes | Adds support for inline code formatting. |
| **`ParagraphFeature`** | Yes | Provides entries in both the slash menu and toolbar dropdown for explicit paragraph creation or conversion. |
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
| **`AlignFeature`** | Yes | Adds support for text alignment (left, center, right, justify) |
| **`IndentFeature`** | Yes | Adds support for text indentation with toolbar buttons |
| **`UnorderedListFeature`** | Yes | Adds support for unordered lists (ul) |
| **`OrderedListFeature`** | Yes | Adds support for ordered lists (ol) |
| **`ChecklistFeature`** | Yes | Adds support for interactive checklists |
| **`LinkFeature`** | Yes | Allows you to create internal and external links |
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
| **`BlockquoteFeature`** | Yes | Allows you to create block-level quotes |
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
| **`HorizontalRuleFeature`** | Yes | Adds support for horizontal rules / separators. Basically displays an `<hr>` element |
| **`InlineToolbarFeature`** | Yes | Provides a floating toolbar which appears when you select text. This toolbar only contains actions relevant for selected text |
| **`FixedToolbarFeature`** | No | Provides a persistent toolbar pinned to the top and always visible. Both inline and fixed toolbars can be enabled at the same time. |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](../fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Provides a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
| **`EXPERIMENTAL_TableFeature`** | No | Adds support for tables. This feature may be removed or receive breaking changes in the future - even within a stable lexical release, without needing a major release. |
| **`TextStateFeature`** | No | Allows you to store key-value attributes within TextNodes and assign them inline styles. |
## In depth
### BoldFeature
- Description: Adds support for bold text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Markdown Support: `**bold**` or `__bold__`
- Keyboard Shortcut: Ctrl/Cmd + B
### ItalicFeature
- Description: Adds support for italic text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Markdown Support: `*italic*` or `_italic_`
- Keyboard Shortcut: Ctrl/Cmd + I
### UnderlineFeature
- Description: Adds support for underlined text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Keyboard Shortcut: Ctrl/Cmd + U
### StrikethroughFeature
- Description: Adds support for strikethrough text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Markdown Support: `~~strikethrough~~`
### SubscriptFeature
- Description: Adds support for subscript text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
### SuperscriptFeature
- Description: Adds support for superscript text formatting, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
### InlineCodeFeature
- Description: Adds support for inline code formatting with distinct styling, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Markdown Support: \`code\`
### ParagraphFeature
- Description: Provides entries in both the slash menu and toolbar dropdown for explicit paragraph creation or conversion.
- Included by default: Yes
### HeadingFeature
- Description: Adds support for heading nodes (H1-H6) with toolbar dropdown and slash menu entries for each enabled heading size.
- Included by default: Yes
- Markdown Support: `#`, `##`, `###`, ..., at start of line.
- Types:
```ts
type HeadingFeatureProps = {
enabledHeadingSizes?: HeadingTagType[] // ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
}
```
- Usage example:
```ts
HeadingFeature({
enabledHeadingSizes: ['h1', 'h2', 'h3'], // Default: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
})
```
### AlignFeature
- Description: Allows text alignment (left, center, right, justify), along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Keyboard Shortcut: Ctrl/Cmd + Shift + L/E/R/J (left/center/right/justify)
### IndentFeature
- Description: Adds support for text indentation, along with buttons to apply it in both fixed and inline toolbars.
- Included by default: Yes
- Keyboard Shortcut: Tab (increase), Shift + Tab (decrease)
- Types:
```ts
type IndentFeatureProps = {
/**
* The nodes that should not be indented. "type"
* property of the nodes you don't want to be indented.
* These can be: "paragraph", "heading", "listitem",
* "quote" or other indentable nodes if they exist.
*/
disabledNodes?: string[]
/**
* If true, pressing Tab in the middle of a block such
* as a paragraph or heading will not insert a tabNode.
* Instead, Tab will only be used for block-level indentation.
* @default false
*/
disableTabNode?: boolean
}
```
- Usage example:
```ts
// Allow block-level indentation only
IndentFeature({
disableTabNode: true,
})
```
### UnorderedListFeature
- Description: Adds support for unordered lists (bullet points) with toolbar dropdown and slash menu entries.
- Included by default: Yes
- Markdown Support: `-`, `*`, or `+` at start of line
### OrderedListFeature
- Description: Adds support for ordered lists (numbered lists) with toolbar dropdown and slash menu entries.
- Included by default: Yes
- Markdown Support: `1.` at start of line
### ChecklistFeature
- Description: Adds support for interactive checklists with toolbar dropdown and slash menu entries.
- Included by default: Yes
- Markdown Support: `- [ ]` (unchecked) or `- [x]` (checked)
### LinkFeature
- Description: Allows creation of internal and external links with toolbar buttons and automatic URL conversion.
- Included by default: Yes
- Markdown Support: `[anchor](url)`
- Types:
```ts
type LinkFeatureServerProps = {
/**
* Disables the automatic creation of links
* from URLs typed or pasted into the editor,
* @default false
*/
disableAutoLinks?: 'creationOnly' | true
/**
* A function or array defining additional
* fields for the link feature.
* These will be displayed in the link editor drawer.
*/
fields?:
| ((args: {
config: SanitizedConfig
defaultFields: FieldAffectingData[]
}) => (Field | FieldAffectingData)[])
| Field[]
/**
* Sets a maximum population depth for the internal
* doc default field of link, regardless of the
* remaining depth when the field is reached.
*/
maxDepth?: number
} & ExclusiveLinkCollectionsProps
type ExclusiveLinkCollectionsProps =
| {
disabledCollections?: CollectionSlug[]
enabledCollections?: never
}
| {
disabledCollections?: never
enabledCollections?: CollectionSlug[]
}
```
- Usage example:
```ts
LinkFeature({
fields: ({ defaultFields }) => [
...defaultFields,
{
name: 'rel',
type: 'select',
options: ['noopener', 'noreferrer', 'nofollow'],
},
],
enabledCollections: ['pages', 'posts'], // Collections for internal links
maxDepth: 2, // Population depth for internal links
disableAutoLinks: false, // Allow auto-conversion of URLs
})
```
### RelationshipFeature
- Description: Allows creation of block-level relationships to other documents with toolbar button and slash menu entry.
- Included by default: Yes
- Types:
```ts
type RelationshipFeatureProps = {
/**
* Sets a maximum population depth for this relationship,
* regardless of the remaining depth when the respective
* field is reached.
*/
maxDepth?: number
} & ExclusiveRelationshipFeatureProps
type ExclusiveRelationshipFeatureProps =
| {
disabledCollections?: CollectionSlug[]
enabledCollections?: never
}
| {
disabledCollections?: never
enabledCollections?: CollectionSlug[]
}
```
- Usage example:
```ts
RelationshipFeature({
disabledCollections: ['users'], // Collections to exclude
maxDepth: 2, // Population depth for relationships
})
```
### UploadFeature
- Description: Allows creation of upload/media nodes with toolbar button and slash menu entry, supports all file types.
- Included by default: Yes
- Types:
```ts
type UploadFeatureProps = {
collections?: {
[collection: CollectionSlug]: {
fields: Field[]
}
}
/**
* Sets a maximum population depth for this upload
* (not the fields for this upload), regardless of
* the remaining depth when the respective field is
* reached.
*/
maxDepth?: number
}
```
- Usage example:
```ts
UploadFeature({
collections: {
uploads: {
fields: [
{
name: 'caption',
type: 'text',
label: 'Caption',
},
{
name: 'alt',
type: 'text',
label: 'Alt Text',
},
],
},
},
maxDepth: 1, // Population depth for uploads
})
```
### BlockquoteFeature
- Description: Allows creation of blockquotes with toolbar button and slash menu entry.
- Included by default: Yes
- Markdown Support: `> quote text`
### HorizontalRuleFeature
- Description: Adds support for horizontal rules/separators with toolbar button and slash menu entry.
- Included by default: Yes
- Markdown Support: `---`
### InlineToolbarFeature
- Description: Provides a floating toolbar that appears when text is selected, containing formatting options relevant to selected text.
- Included by default: Yes
### FixedToolbarFeature
- Description: Provides a persistent toolbar pinned to the top of the editor that's always visible.
- Included by default: No
- Types:
```ts
type FixedToolbarFeatureProps = {
/**
* @default false
* If this is enabled, the toolbar will apply
* to the focused editor, not the editor with
* the FixedToolbarFeature.
*/
applyToFocusedEditor?: boolean
/**
* Custom configurations for toolbar groups
* Key is the group key (e.g. 'format', 'indent', 'align')
* Value is a partial ToolbarGroup object that will
* be merged with the default configuration
*/
customGroups?: CustomGroups
/**
* @default false
* If there is a parent editor with a fixed toolbar,
* this will disable the toolbar for this editor.
*/
disableIfParentHasFixedToolbar?: boolean
}
```
- Usage example:
```ts
FixedToolbarFeature({
applyToFocusedEditor: false, // Apply to focused editor
customGroups: {
format: {
// Custom configuration for format group
},
},
})
```
### BlocksFeature
- Description: Allows use of Payload's Blocks Field directly in the editor with toolbar buttons and slash menu entries for each block type.
- Included by default: No
- Types:
```ts
type BlocksFeatureProps = {
blocks?: (Block | BlockSlug)[] | Block[]
inlineBlocks?: (Block | BlockSlug)[] | Block[]
}
```
- Usage example:
```ts
BlocksFeature({
blocks: [
{
slug: 'callout',
fields: [
{
name: 'text',
type: 'text',
required: true,
},
],
},
],
inlineBlocks: [
{
slug: 'mention',
fields: [
{
name: 'name',
type: 'text',
required: true,
},
],
},
],
})
```
### TreeViewFeature
- Description: Provides a debug panel below the editor showing the editor's internal state, DOM tree, and time travel debugging.
- Included by default: No
### EXPERIMENTAL_TableFeature
- Description: Adds support for tables with toolbar button and slash menu entry for creation and editing.
- Included by default: No
### TextStateFeature
- Description: Allows storing key-value attributes in text nodes with inline styles and toolbar dropdown for style selection.
- Included by default: No
- Types:
```ts
type TextStateFeatureProps = {
/**
* The keys of the top-level object (stateKeys) represent the attributes that the textNode can have (e.g., color).
* The values of the top-level object (stateValues) represent the values that the attribute can have (e.g., red, blue, etc.).
* Within the stateValue, you can define inline styles and labels.
*/
state: { [stateKey: string]: StateValues }
}
type StateValues = {
[stateValue: string]: {
css: StyleObject
label: string
}
}
type StyleObject = {
[K in keyof PropertiesHyphenFallback]?:
| Extract<PropertiesHyphenFallback[K], string>
| undefined
}
```
- Usage example:
```ts
// We offer default colors that have good contrast and look good in dark and light mode.
import { defaultColors, TextStateFeature } from '@payloadcms/richtext-lexical'
TextStateFeature({
// prettier-ignore
state: {
color: {
...defaultColors,
// fancy gradients!
galaxy: { label: 'Galaxy', css: { background: 'linear-gradient(to right, #0000ff, #ff0000)', color: 'white' } },
sunset: { label: 'Sunset', css: { background: 'linear-gradient(to top, #ff5f6d, #6a3093)' } },
},
// You can have both colored and underlined text at the same time.
// If you don't want that, you should group them within the same key.
// (just like I did with defaultColors and my fancy gradients)
underline: {
'solid': { label: 'Solid', css: { 'text-decoration': 'underline', 'text-underline-offset': '4px' } },
// You'll probably want to use the CSS light-dark() utility.
'yellow-dashed': { label: 'Yellow Dashed', css: { 'text-decoration': 'underline dashed', 'text-decoration-color': 'light-dark(#EAB308,yellow)', 'text-underline-offset': '4px' } },
},
},
}),
```
This is what the example above will look like:
<LightDarkImage
srcDark="https://payloadcms.com/images/docs/text-state-feature.png"
srcLight="https://payloadcms.com/images/docs/text-state-feature.png"
alt="Example usage in light and dark mode for TextStateFeature with defaultColors and some custom styles"
/>

View File

@@ -138,39 +138,9 @@ import { CallToAction } from '../blocks/CallToAction'
| **`defaultFeatures`** | This opinionated array contains all "recommended" default features. You can see which features are included in the default features in the table below. |
| **`rootFeatures`** | This array contains all features that are enabled in the root richText editor (the one defined in the payload.config.ts). If this field is the root richText editor, or if the root richText editor is not a lexical editor, this array will be empty. |
## Features overview
## Official Features
Here's an overview of all the included features:
| Feature Name | Included by default | Description |
| ----------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`BoldFeature`** | Yes | Handles the bold text format |
| **`ItalicFeature`** | Yes | Handles the italic text format |
| **`UnderlineFeature`** | Yes | Handles the underline text format |
| **`StrikethroughFeature`** | Yes | Handles the strikethrough text format |
| **`SubscriptFeature`** | Yes | Handles the subscript text format |
| **`SuperscriptFeature`** | Yes | Handles the superscript text format |
| **`InlineCodeFeature`** | Yes | Handles the inline-code text format |
| **`ParagraphFeature`** | Yes | Handles paragraphs. Since they are already a key feature of lexical itself, this Feature mainly handles the Slash and Add-Block menu entries for paragraphs |
| **`HeadingFeature`** | Yes | Adds Heading Nodes (by default, H1 - H6, but that can be customized) |
| **`AlignFeature`** | Yes | Allows you to align text left, centered and right |
| **`IndentFeature`** | Yes | Allows you to indent text with the tab key |
| **`UnorderedListFeature`** | Yes | Adds unordered lists (ul) |
| **`OrderedListFeature`** | Yes | Adds ordered lists (ol) |
| **`ChecklistFeature`** | Yes | Adds checklists |
| **`LinkFeature`** | Yes | Allows you to create internal and external links |
| **`RelationshipFeature`** | Yes | Allows you to create block-level (not inline) relationships to other documents |
| **`BlockquoteFeature`** | Yes | Allows you to create block-level quotes |
| **`UploadFeature`** | Yes | Allows you to create block-level upload nodes - this supports all kinds of uploads, not just images |
| **`HorizontalRuleFeature`** | Yes | Horizontal rules / separators. Basically displays an `<hr>` element |
| **`InlineToolbarFeature`** | Yes | The inline toolbar is the floating toolbar which appears when you select text. This toolbar only contains actions relevant for selected text |
| **`FixedToolbarFeature`** | No | This classic toolbar is pinned to the top and always visible. Both inline and fixed toolbars can be enabled at the same time. |
| **`BlocksFeature`** | No | Allows you to use Payload's [Blocks Field](../fields/blocks) directly inside your editor. In the feature props, you can specify the allowed blocks - just like in the Blocks field. |
| **`TreeViewFeature`** | No | Adds a debug box under the editor, which allows you to see the current editor state live, the dom, as well as time travel. Very useful for debugging |
| **`EXPERIMENTAL_TableFeature`** | No | Adds support for tables. This feature may be removed or receive breaking changes in the future - even within a stable lexical release, without needing a major release. |
| **`EXPERIMENTAL_TextStateFeature`** | No | Allows you to store key-value attributes within TextNodes and assign them inline styles. |
Notice how even the toolbars are features? That's how extensible our lexical editor is - you could theoretically create your own toolbar if you wanted to!
You can find more information about the official features in our [official features docs](../rich-text/official-features).
## Creating your own, custom Feature
@@ -299,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

200
docs/trash/overview.mdx Normal file
View File

@@ -0,0 +1,200 @@
---
title: Trash
label: Overview
order: 10
desc: Enable soft deletes for your collections to mark documents as deleted without permanently removing them.
keywords: trash, soft delete, deletedAt, recovery, restore
---
Trash (also known as soft delete) allows documents to be marked as deleted without being permanently removed. When enabled on a collection, deleted documents will receive a `deletedAt` timestamp, making it possible to restore them later, view them in a dedicated Trash view, or permanently delete them.
Soft delete is a safer way to manage content lifecycle, giving editors a chance to review and recover documents that may have been deleted by mistake.
<Banner type="warning">
**Note:** The Trash feature is currently in beta and may be subject to change
in minor version updates.
</Banner>
## Collection Configuration
To enable soft deleting for a collection, set the `trash` property to `true`:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts',
trash: true,
fields: [
{
name: 'title',
type: 'text',
},
// other fields...
],
}
```
When enabled, Payload automatically injects a deletedAt field into the collection's schema. This timestamp is set when a document is soft-deleted, and cleared when the document is restored.
## Admin Panel behavior
Once `trash` is enabled, the Admin Panel provides a dedicated Trash view for each collection:
- A new route is added at `/collections/:collectionSlug/trash`
- The `Trash` view shows all documents that have a `deletedAt` timestamp
From the Trash view, you can:
- Use bulk actions to manage trashed documents:
- **Restore** to clear the `deletedAt` timestamp and return documents to their original state
- **Delete** to permanently remove selected documents
- **Empty Trash** to select and permanently delete all trashed documents at once
- Enter each document's **edit view**, just like in the main list view. While in the edit view of a trashed document:
- All fields are in a **read-only** state
- Standard document actions (e.g., Save, Publish, Restore Version) are hidden and disabled.
- The available actions are **Restore** and **Permanently Delete**.
- Access to the **API**, **Versions**, and **Preview** views is preserved.
When deleting a document from the main collection List View, Payload will soft-delete the document by default. A checkbox in the delete confirmation modal allows users to skip the trash and permanently delete instead.
## API Support
Soft deletes are fully supported across all Payload APIs: **Local**, **REST**, and **GraphQL**.
The following operations respect and support the `trash` functionality:
- `find`
- `findByID`
- `update`
- `updateByID`
- `delete`
- `deleteByID`
- `findVersions`
- `findVersionByID`
### Understanding `trash` Behavior
Passing `trash: true` to these operations will **include soft-deleted documents** in the query results.
To return _only_ soft-deleted documents, you must combine `trash: true` with a `where` clause that checks if `deletedAt` exists.
### Examples
#### Local API
Return all documents including trashed:
```ts
const result = await payload.find({
collection: 'posts',
trash: true,
})
```
Return only trashed documents:
```ts
const result = await payload.find({
collection: 'posts',
trash: true,
where: {
deletedAt: {
exists: true,
},
},
})
```
Return only non-trashed documents:
```ts
const result = await payload.find({
collection: 'posts',
trash: false,
})
```
#### REST
Return **all** documents including trashed:
```http
GET /api/posts?trash=true
```
Return **only trashed** documents:
```http
GET /api/posts?trash=true&where[deletedAt][exists]=true
```
Return only non-trashed documents:
```http
GET /api/posts?trash=false
```
#### GraphQL
Return all documents including trashed:
```ts
query {
Posts(trash: true) {
docs {
id
deletedAt
}
}
}
```
Return only trashed documents:
```ts
query {
Posts(
trash: true
where: { deletedAt: { exists: true } }
) {
docs {
id
deletedAt
}
}
}
```
Return only non-trashed documents:
```ts
query {
Posts(trash: false) {
docs {
id
deletedAt
}
}
}
```
## Access Control
All trash-related actions (delete, permanent delete) respect the `delete` access control defined in your collection config.
This means:
- If a user is denied delete access, they cannot soft delete or permanently delete documents
## Versions and Trash
When a document is soft-deleted:
- It can no longer have a version **restored** until it is first restored from trash
- Attempting to restore a version while the document is in trash will result in an error
- This ensures consistency between the current document state and its version history
However, versions are still fully **visible and accessible** from the **edit view** of a trashed document. You can view the full version history, but must restore the document itself before restoring any individual version.

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