Previously, we were calling `getNavPrefs` (a payload.find call) three times for every single page load.
This PR:
1. Ensures that `getNavPrefs` is called only once per page load, reducing two unnecessary `payload.find` calls every time a page is loaded or navigated to.
2. Adds `pagination: false` to the `payload.find` call, making it more efficient and improving performance.
## How?
We were using React's cache to ensure that navigation preferences (`getNavPrefs`) were fetched only once per request. However, this wasn't working as expected because the first argument of `getNavPrefs` was an object. Each time it was called, a new object reference was passed, preventing React from caching it properly.
To fix this, this PR ensures that only primitive values are used as arguments for caching, following best practices and making the cache function work as intended.
Just like https://github.com/payloadcms/payload/pull/11269, we stop passing through `req` to db operations in `checkDocumentLockStatus`.
After extensive testing, this seems to get rid of all transaction errors that occurred when I was testing autosave against a remote mongo DB.
We keep the `req` for postgres, as it mysteriously breaks in CI - this cannot be reproduced locally
## getPreferences function caching
Our `getPreferences` function used in the ui package is now wrapped in react cache, to minimize the amount of times it runs on a single request. This mimics the behavior of our other `getPreferences` function in the next package.
## getPreferences incorrect behavior
The `getPreferences` function in the next package was passing through the incorrect user slug. This would not have been noticeable in projects with just one users collection, but might break in projects with multiple users collections.
## getPreferences performance optimization
This PR adds `pagination: false` to the getPreferences payload.find() call, which will speed up the query.
## upsertPreferences transaction errors
Due to the potential of preference upsert operations running in parallel (e.g. when switching locales), this PR disables transactions in the preferences creation / update calls. This fixes the transaction errors reported in https://github.com/payloadcms/payload/issues/11310
When `/uploads/file/file-path.jpg` endpoint is requested, and
`file-path.jpg` _was found_ in the database, but was not found on the
disk, the error like this is printed to the console:
```
[03:34:54] ERROR: ENOENT: no such file or directory, stat '/Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png'
err: {
"type": "Error",
"message": "ENOENT: no such file or directory, stat '/Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png'",
"stack":
Error: ENOENT: no such file or directory, stat '/Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png'
at async Object.stat (node:internal/fs/promises:1037:18)
at async getFileHandler (webpack-internal:///(rsc)/./packages/payload/src/uploads/endpoints/getFile.ts:59:19)
at async handleEndpoints (webpack-internal:///(rsc)/./packages/payload/src/utilities/handleEndpoints.ts:178:26)
at async eval (webpack-internal:///(rsc)/./packages/next/src/routes/rest/index.ts:27:26)
at async AppRouteRouteModule.do (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:10:32847)
at async AppRouteRouteModule.handle (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:10:39868)
at async doRender (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:1452:42)
at async responseGenerator (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:1822:28)
at async DevServer.renderToResponseWithComponentsImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:1832:28)
at async DevServer.renderPageComponent (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:2259:24)
at async DevServer.renderToResponseImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:2297:32)
at async DevServer.pipeImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:959:25)
at async NextNodeServer.handleCatchallRenderRequest (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/next-server.js:281:17)
at async DevServer.handleRequestImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:853:17)
at async /Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/dev/next-dev-server.js:371:20
at async Span.traceAsyncFn (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/trace/trace.js:153:20)
at async DevServer.handleRequest (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/dev/next-dev-server.js:368:24)
at async invokeRender (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/lib/router-server.js:230:21)
at async handleRequest (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/lib/router-server.js:408:24)
at async NextCustomServer.requestHandlerImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/lib/router-server.js:432:13)
at async Server.<anonymous> (file:///Users/sasha/work/payload/test/dev.ts:1:2848)
"errno": -2,
"code": "ENOENT",
"syscall": "stat",
"path": "/Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png"
}
```
Which is quite noisy as we understand why this error happens (and it
might be intentional when you load production DB to local and don't have
any files)
Now, the logging here in case of `ENOENT` is simplified to this:
```
[03:43:35] ERROR: File Screenshot 2025-02-19 at 18.50.46.png for collection uploads-1 is missing on the disk. Expected path: /Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png
```
Fixes https://github.com/payloadcms/payload/issues/6246
### What?
Adds JSDoc for options of all collection Local API operations
_Every_ property now is documented there, even those that aren't on our
website docs.
### Why?
This is useful to have, now you can hover over any property to see what
it does in your editor.
Some properties also link to the documentation website directly for more
info.
### How?
Updates every collection operation arguments definition with JSDoc.
For globals will be a separate PR.
<!--
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 the `admin.group` property in block
configs. This property enables blocks to be grouped under a common,
potentially localized, label in the block drawer component. This makes
it easier to sort through large collections of blocks. Previously, all
blocks would be in one common layout.
This PR also encompasses documentation changes and e2e tests to check
for the rendering of group labels.
### Why?
To make it easier to organize many blocks in block fields.
### How?
By introducing a new `admin.group` property in block configs and
assembling them in the blocks drawer component.
Before:

After:

Demo:
[Editing---Block-Field---Payload-groups-demo.webm](https://github.com/user-attachments/assets/2b351dc1-0d14-4a5b-ae71-bcd31fbb23df)
Addresses #5609
When the new ConfirmationModal is used outside of the context of an
EditDepthProvider, it stacks behind currently open modals. This is
apparent when using it in custom views.
The fix is to build the confirm modal's depth off of drawerDepth instead
of editDepth.
`getRequestLocale` => `upsertPreferences` is already called as part of `initReq`, yet we were still unnecessarily calling `getRequestLocale` afterwards, which potentially resulted in at least one unnecessary `payload.find()` or `payload.update()` call.
### What?
Fixes translation errors that are thrown when JSON field validation
outputs an error.
### How?
Removes translation function `t()` from wrapping the errors and adds
translation keys that were missing from `clientKeys.ts`.
Fixes#10543
### What?
We had an `allowCreate` prop for the list drawer that doesn't do
anything. This PR passes the prop through so it can be used.
### How?
Passes `allowCreate` down to the list view and ties it with
`hasCreatePermission`
#### Testing
- Use `admin` test suite and `withListDrawer` collection.
- Test added to the `admin/e2e/list-view`.
Fixes#11246
### What?
Adding `assetPrefix` to the `next.config` prevents the hot module
reloading functionality.
### Why & How?
Need to incorporate `assetPrefix` into the URL generated for webpack
HMR.
Fixes#11150
#### Testing
1. Add `assetPrefix: '/test'` to the `next.config.mjs` in the root
folder
2. Run `pnpm test _community`
3. Go to the `_community/collections/posts` config and change a field
4. Open post collection in browser and see no change (if this PR is
checked out then you _**will**_ see the change)
### What?
This PR removes references to the `rateLimit` option from the
documentation, as it was deprecated in Payload v3.
Since Payload now runs on Next.js, which are often deployed
serverlessly, built-in rate limiting is no longer supported.
Users are encouraged to implement rate limiting at the load balancer,
proxy level, or use services like Cloudflare.
Fixes#10321
The following MDX:
```tsx
<Banner type='info'>
Hello
</Banner>
```
was not able to be parsed by the lexical mdx converter, as the jsx props string extractor did not support the single quotes around the `info` string.
This PR fixes the `UploadData` type that was weakened in a previous PR, causing a breaking change. It also improves the newly added `UploadDataImproved` type by bringing back its support for generated types and using the `UploadCollectionSlug` type helper to restrict collection slugs to upload-enabled collections.
### What?
The admin panel was not respecting where constraints returned from the
readAccess function.
### Why?
`getEntityPolicies` was always using `find` when looping over the
operations, but `readVersions` should be using `findVersions`.
### How?
When the operation is `readVersions` run the `findVersions` operation.
Fixes https://github.com/payloadcms/payload/issues/11240
### What?
When you first edit a document and then open the Schedule publish
drawer, you can schedule publish changes but the current changes made to
the form won't be included.
### Why?
The UX does not make it clear that the changes you have in the form are
not actually going to be published.
### How?
Instead of allowing that we just disable the Schedule Publish drawer
toggler so that users are forced to save a draft first.
In addition to the above, this change also passes a defaultType so that
an already published document will default the radio type have
"Unpublish" selected.
Restoring a version has two types of messages, success and error, but no
matter if this action is a success or a failure, the toast message is
never displayed.
The fix is to import the toast from `@payloadcms/ui` instead of `sonner`
directly.
Fixes#11059
Fixes https://github.com/payloadcms/payload/issues/10962
This fix addresses fields with timezones enabled specifically for not
time pickers. If all you want to do is pick a date such as 14th Feb, it
would store the incorrect version and display a date in the future for
people in the Pacific.
This is because Auckland is +12 offset, but +13 with Daylight Savings
Time. In our date picker we try to normalise date pickers with no time
to 12pm and so half the year we ended up pushing dates visually to the
next day for people in the pacific only. Other regions were not affected
by this because their offset would be less than 12.
This PR fixes this by ensuring that our dates are always normalised to
selected timezone's 12pm date to UTC.
There's also additional tests for these two fields from 3 main locations
to cover a wider range of possible timezones.
Just like https://github.com/payloadcms/payload/pull/11269, we stop
passing through `req` to db operations in `checkDocumentLockStatus`.
After extensive testing, this seems to get rid of all transaction errors
that occurred when I was testing autosave against a remote mongo DB.
Removes unnecessary callback args from the `onConfirm` callback in the
new `ConfirmationModal` component. Now, the component will close and
reset `isConfirming` state for itself.
<!--
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 #
-->
There are nearly a dozen independent implementations of the same modal
spread throughout the admin panel and various plugins. These modals are
used to confirm or cancel an action, such as deleting a document, bulk
publishing, etc. Each of these instances is nearly identical, leading to
unnecessary development efforts when creating them, inconsistent UI, and
duplicative stylesheets.
Everything is now standardized behind a new `ConfirmationModal`
component. This modal comes with a standard API that is flexible enough
to replace nearly every instance. This component has also been exported
for reuse.
Here is a basic example of how to use it:
```tsx
'use client'
import { ConfirmationModal, useModal } from '@payloadcms/ui'
import React, { Fragment } from 'react'
const modalSlug = 'my-confirmation-modal'
export function MyComponent() {
const { openModal } = useModal()
return (
<Fragment>
<button
onClick={() => {
openModal(modalSlug)
}}
type="button"
>
Do something
</button>
<ConfirmationModal
heading="Are you sure?"
body="Confirm or cancel before proceeding."
modalSlug={modalSlug}
onConfirm={({ closeConfirmationModal, setConfirming }) => {
// do something
setConfirming(false)
closeConfirmationModal()
}}
/>
</Fragment>
)
}
```
## Change 1 - database errors when running autosave
The previous autosave implementation allowed multiple autosave fetch
calls (=> save document draft) to run in parallel. While the
AbortController aborted previous autosave calls if a new one comes in in
order to only process the latest one, this had one flaw:
Using the AbortController to abort the autosave call only aborted the
`fetch` call - it did not however abort the database operation that may
have started as part of this fetch call. If you then started a new
autosave call, this will start yet another database operation on the
backend, resulting in two database operations that would be running at
the same time.
This has caused a lot of transaction errors that were only noticeable
when connected to a slower, remote database. This PR removes the
AbortController and ensures that the previous autosave operation is
properly awaited before starting a new one, while still discarding
outdated autosave requests from the queue **that have not started yet**.
Additionally, it cleans up the AutoSave component to make it more
readable.
## Change 2 - ensure autosave doesn't run unnecessarily
If connected to a slower backend or database, one change in a document
may trigger two autosave operations instead of just one. This is how it
could happen:
1. Type something => formstate changes => autosave is triggered
2. 200ms later: form state request is triggered. Autosave is still
processing
3. 100ms later: form state comes back from server => local form state is
updated => another autosave is triggered
4. First autosave is aborted - this lead to a browser error. This PR
ensures that that error is no longer surfaced to the user
5. Another autosave is started
This PR adds additional checks to ONLY trigger an autosave if the form
DATA (not the entire form state itself) changes. Previously, it ran
every time the object reference of the form state changes. This includes
changes that do not affect the form data, like `field.valid`. =>
Basically every time form state comes back from the server, we were
triggering another, unnecessary autosave
Not passing through `req` ensures that the db operations in
`handleFormStateLocking` run independently, preventing them from being
part of the same transaction. Since locked document operations don't
really require transactional consistency, this change helps avoid
unnecessary transaction errors that have previously occurred here.
In the `findOne` db operation, we return `null` if the document was not
found.
For single-document delete and update operations, if the document you
wanted to update is not found, the following runtime error is thrown
instead: `Cannot read properties of null (reading '_id')`.
This PR correctly handles these cases and returns `null` from the db
method, just like the `findOne` operation.
### What?
Updates the join field documentation.
Mentions:
* Now you can specify an array of `collection` -
https://github.com/payloadcms/payload/pull/10919
* Querying limitation for join fields, planned
https://github.com/payloadcms/payload/discussions/9683
* Querying limitation for joined documents when the join field has an
array of `collection` for fields inside arrays and blocks.
### Why?
To have up to date documentation for an array of `collection` and so
users can know about limitations.
### How?
Updates the file on path `docs/fields/join.mdx`.
Similar to the goals of #11026. Adds helper utilities to make
interacting with the blocks field easier within e2e tests. This will
also standardize common functionality across tests and reduce the
overall lines of code for each, making them easier to navigate and
digest.
The following helpers are now available:
- `openBlocksDrawer`: self-explanatory
- `addBlock`: opens the blocks drawer and selects the given block
- `reorderBlocks`: similar to `reorderColumn`, moves blocks using the
drag handle
- `removeAllBlocks`: iterates all rows of a given blocks field and
removes them
This feature allows you to specify `collection` for the join field as
array.
This can be useful for example to describe relationship linking like
this:
```ts
{
slug: 'folders',
fields: [
{
type: 'join',
on: 'folder',
collection: ['files', 'documents', 'folders'],
name: 'children',
},
{
type: 'relationship',
relationTo: 'folders',
name: 'folder',
},
],
},
{
slug: 'files',
upload: true,
fields: [
{
type: 'relationship',
relationTo: 'folders',
name: 'folder',
},
],
},
{
slug: 'documents',
fields: [
{
type: 'relationship',
relationTo: 'folders',
name: 'folder',
},
],
},
```
Documents and files can be placed to folders and folders themselves can
be nested to other folders (root folders just have `folder` as `null`).
Output type of `Folder`:
```ts
export interface Folder {
id: string;
children?: {
docs?:
| (
| {
relationTo?: 'files';
value: string | File;
}
| {
relationTo?: 'documents';
value: string | Document;
}
| {
relationTo?: 'folders';
value: string | Folder;
}
)[]
| null;
hasNextPage?: boolean | null;
} | null;
folder?: (string | null) | Folder;
updatedAt: string;
createdAt: string;
}
```
While you could instead have many join fields (for example
`childrenFolders`, `childrenFiles`) etc - this doesn't allow you to
sort/filter and paginate things across many collections, which isn't
trivial. With SQL we use `UNION ALL` query to achieve that.
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
Previously, in postgres query like:
```ts
const result = await payload.find({
collection: 'blocks',
where: { 'blocks.director.name': { equals: 'Test Director' } },
})
```
where `blocks` is a blocks field, `director` is a relationship field and
`name` is a text field inside `directors`, failed with:

### Why?
The generated query before was a bit wrong.
Before:
```sql
select distinct
"blocks"."id",
"blocks"."created_at",
"blocks"."created_at"
from
"blocks"
left join "directors" "a5ad426a_eda4_4067_af7e_5b294d7f0968" on "a5ad426a_eda4_4067_af7e_5b294d7f0968"."id" = "blocks_blocks_some"."director_id"
left join "blocks_blocks_some" on "blocks"."id" = "blocks_blocks_some"."_parent_id"
where
"a5ad426a_eda4_4067_af7e_5b294d7f0968"."name" = 'Test Director'
order by
"blocks"."created_at" desc
limit
10
```
Notice `left join directors` _before_ join of `blocks_blocks_some`.
`blocks_blocks_some` doesn't exist yet, this PR changes so now we
generate
```sql
select distinct
"blocks"."id",
"blocks"."created_at",
"blocks"."created_at"
from
"blocks"
left join "blocks_blocks_some" on "blocks"."id" = "blocks_blocks_some"."_parent_id"
left join "directors" "a5ad426a_eda4_4067_af7e_5b294d7f0968" on "a5ad426a_eda4_4067_af7e_5b294d7f0968"."id" = "blocks_blocks_some"."director_id"
where
"a5ad426a_eda4_4067_af7e_5b294d7f0968"."name" = 'Test Director'
order by
"blocks"."created_at" desc
limit
10
```
Fixes https://github.com/payloadcms/payload/issues/11224
Fixes https://github.com/payloadcms/payload/issues/10492
This PR fixes a few weird behaviours when `validate: true` is set on drafts:
- when autosave is on and you submit an invalid form it would get stuck in an infinite loop
- PreventLeave would not trigger for submitted but invalid forms leading to potential data loss
Changes:
- Adds e2e tests for the above scenarios
- Adds a new `isValid` flag on the `Form` context provider to signal globally if the form is in a valid or invalid state
- Components like Autosave will manage this internally since it manages its own submission flow as well
- Adds PreventLeave to Autosave too for when form is invalid meaning data hasn't been actually saved so we want to prevent the user accidentally losing data by reloading or closing the page
The following tests have been added

When reusing the SelectInput component from the UI package, if you set
value to `''` it will continue to display the previously selected value
instead of clearing out the field as expected.
The ReactSelect component doesn't behave in this way and instead will
clear out the field.
This fix addresses this difference by resetting `valueToRender` inside
the SelectInput to null.
This PR optimizes permissions calculation for block references, by
calculating them only once per block reference config, instead of once
every single time the blocks are referenced.
This will lead to significant performance improvements in Payload
Configs with a lot of duplicative block references, as permissions are
calculated every time you navigate from page to page.
# Benchmarks
Tested using `pnpm dev benchmark-blocks`.
## Before - ~ 6 seconds
https://github.com/user-attachments/assets/85cac698-3120-414f-91d3-608a404a3a5f
## After - ~ 2 seconds
https://github.com/user-attachments/assets/0c3642f6-6001-41ae-a7cd-f30b24362e9b
### What?
This PR adds missing languages to `plugin-seo` that are supported in
Payload but were not ported to the plugin.
### Why?
To properly translate the custom keys added by the plugin in all
languages currently supported.
### How?
By adding the missing languages and exporting them for use.
Addresses #11201
### What?
After clicking "Select all" `toggleAll(true)`, manually deselecting an
item does not update the overall selection status.
The bulk actions remain visible, and `selectAll` incorrectly stays as
`AllAvailable`.
### How?
Updated `setSelection()` logic to adjust `selectAll` when deselecting an
item if it was previously set to `AllAvailable`.
This ensures that the selection state updates correctly without altering
the effect logic.
`selectAll` switches to Some when an item is deselected after selecting
all.
Bulk actions now hide correctly if no items are selected.
Fixes#10836
Currently, the join field outputs to its result `hasNextPage: boolean`
and have the `limit` query parameter but lacks `page` which can be
useful. This PR adds it.
### What?
Adds new option `admin.components.listMenuItems` to allow custom
components to be injected after the existing list controls in the
collection list view.
### Why?
Needed to facilitate import/export plugin.
#### Testing
Use `pnpm dev admin` to see example component and see test added to
`test/admin/e2e/list-view`.
## Update since feature was reverted
The custom list controls and now rendered with no surrounding padding or
border radius.
<img width="596" alt="Screenshot 2025-02-17 at 5 06 44 PM"
src="https://github.com/user-attachments/assets/57209367-5433-4a4c-8797-0f9671da15c8"
/>
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>