This PR introduced https://github.com/payloadcms/payload/pull/11952
improvement for graphql schema with making fields of the `Paginated<T>`
interface non-nullable.
However, there are a few special ones - `nextPage` and `prevPage`. They
can be `null` when:
The result returned 0 docs.
The result returned `x` docs, but in the DB we don't have `x+1` doc.
Thus, `nextPage` will be `null`. The result will have `nextPage: null`.
Finally, when we query 1st page, `prevPage` is `null` as well.
<img width="873" alt="image"
src="https://github.com/user-attachments/assets/04d04b13-ac26-4fc1-b421-b5f86efc9b65"
/>
When `payload migrate` is run and a record with name "dev" is returned
having `batch: -1`, then the `batch` is not incrementing as expected as
it is stuck at 1. This change makes it so the batch is incremented from
the correct latest batch, ignoring the `name: "dev"` migration.
### What?
Adds line-breaks after headings, lists, list items, tables, table rows,
and table cells when converting lexical content to plaintext.
### Why?
Currently text from those nodes is concatenated without a separator.
### How?
Adds handling for these nodes to the plain text converter.
### What?
Selected documents in a relationship field can be opened in a new tab.
### Why?
Related documents can be edited using the edit icon which opens the
document in a drawer. Sometimes users would like to open the document in
a new tab instead to e.g. modify the related document at a later point
in time. This currently requires users to find the related document via
the list view and open it there. There is no easy way to find and open a
related document.
### How?
Adds custom handling to the relationship edit button to support opening
it in a new tab via middle-click, Ctrl+click, or right-click → 'Open in
new tab'.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
In this case, the `blockType` property is created on the server, but -
prior to this fix - was discarded on the client in
[`fieldReducer.ts`](https://github.com/payloadcms/payload/blob/main/packages/ui/src/forms/Form/fieldReducer.ts#L186-L198)
via
[`mergerServerFormState.ts`](b9832f40e4/packages/ui/src/forms/Form/mergeServerFormState.ts (L29-L31)),
because the field's path neither existed in the client's form state, nor
was it marked as `addedByServer`.
This caused later calls to POST requests to form state to send without
the `blockType` key for block rows, which in turn caused
`addFieldStatePromise.ts` to throw the following error:
```
Block with type "undefined" was found in block data, but no block with that type is defined in the config for field with schema path ${schemaPath}.
```
This prevented the client side form state update from completing, and if
the form state was saved, broke the document.
This is a follow-up to #12131, which treated the symptom, but not the
cause. The original issue seems to have been introduced in
https://github.com/payloadcms/payload/releases/tag/v3.34.0. It's unclear
to me whether this issue is connected to block E2E tests having been
disabled in the same release in
https://github.com/payloadcms/payload/pull/11988.
## How to reproduce
### Collection configuration
```ts
const RICH_TEXT_BLOCK_TYPE = 'richTextBlockType'
const RichTextBlock: Block = {
slug: RICH_TEXT_BLOCK_TYPE,
interfaceName: 'RichTextBlock',
fields: [
{
name: 'richTextBlockField',
label: 'Rich Text Field in Block Field',
type: 'richText',
editor: lexicalEditor({}),
required: true,
},
],
}
const MyCollection: CollectionConfig = {
slug: 'my-collection-slug,
fields: [
{
name: 'arrayField',
label: 'Array Field',
type: 'array',
fields: [
{
name: 'blockField',
type: 'blocks',
blocks: [RichTextBlock],
required: true,
},
],
},
]
}
export default MyCollection
```
### Steps
- Press "Add Array Field"
--> ✅ 1st block with rich text is added
- Press "Add Array Field" a 2nd time
### Result
- 🛑 2nd block is indefinitely in loading state (side-note: the form UI
should preferably explicitly indicate the error).
- 🛑 If saving the document, it is corrupted and will only show a blank
page (also not indicating any error).
Client side:
<img width="1268" alt="Untitled"
src="https://github.com/user-attachments/assets/4b32fdeb-af76-41e2-9181-d2dbd686618a"
/>
API error:
<img width="1272" alt="image"
src="https://github.com/user-attachments/assets/35dc65f7-88ac-4397-b8d4-353bcf6a4bfd"
/>
Client side, when saving and re-opening document (API error of `GET
/admin/collections/${myCollection}/${documentId}` is the same (arguably
the HTTP response status code shouldn't be `200`)):
<img width="1281" alt="image"
src="https://github.com/user-attachments/assets/2e916eb5-6f10-4e82-9b84-1dc41db21d47"
/>
### Result after fix
- `blockType` is sent from the client to the server.
- ✅ 2nd block with rich text is added.
- ✅ Document does not break when saving & re-opening.
<img width="1277" alt="Untitled"
src="https://github.com/user-attachments/assets/84d0c88b-64b2-48c4-864d-610d524ac8fc"
/>
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
### What?
Fixes the label for documents which were the current published document
but got unpublished in the version view.
### Why?
If the most recent published document was unpublished, it remained
displayed as "Currently published version" in the version list.
### How?
Checks whether the document has a currently published version instead of
only looking at the latest published version when determining the label
in the versions view.
Fixes https://github.com/payloadcms/payload/issues/10838
---------
Co-authored-by: Alessio Gravili <alessio@gravili.de>
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
Fix link to "Blank Template" in installation.mdx so that it displays
correctly on the web.
### Why?
Text of broken md link looks bad.
### How?
Remove angle brackets.
### Fixes:

### What?
Allows array fields to be filtered in the list view.
### Why?
Array fields were not filterable in the list view although all other
field types were filterable already.
### How?
Adds handling for array fields as filter option.

Fixes population of joins that target relationship fields that have
`relationTo` as an array, for example:
```ts
// Posts collection
{
name: 'polymorphic',
type: 'relationship',
relationTo: ['categories', 'users'],
},
// Categories collection
{
name: 'polymorphic',
type: 'join',
collection: 'posts',
on: 'polymorphic',
}
```
Thanks @jaycetde for the integration test
https://github.com/payloadcms/payload/pull/12278!
---------
Co-authored-by: Jayce Pulsipher <jpulsipher@nav.com>
### What?
Using `create-payload-app` to initialize Payload in an existing Next.js
app **that does not already have Payload installed** overwrites any
existing data in the `.env` and `.env.example` files.
The desired behavior is for Payload variables to get added with no
client data lost.
### How?
Updates `manageEnvFiles` to check for existing `.env / .env.example`
file and appends or creates as necessary.
Adds tests to
`packages/create-payload-app/src/lib/create-project.spec.ts`.
#### Fixes https://github.com/payloadcms/payload/issues/10355
### What?
As described in https://github.com/payloadcms/payload/discussions/10946,
allow passing a custom `collectionPopulationRequestHandler` function to
`subscribe`, which passes it along to `handleMessage` and `mergeData`
### Why?
`mergeData` already supports a custom function for this, that
functionality however isn't exposed.
My use case so far was passing along custom Authorization headers.
### How?
Move the functions type defined in `mergeData` to a dedicated
`CollectionPopulationRequestHandler` type, reuse it across `subscribe`,
`handleMessage` and `mergeData`.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Improves performance in local strategy uploads by reading the file and
metadata info synchronously. This change uses `promise.all` for three
separately awaited calls. This improves the perf by making all calls in
a non-blocking way.
Previously, duplication with orderable collections worked incorrectly,
for example
Document 1 is created - `_order: 'a5'`
Document 2 is duplicated from 1, - `_order: 'a5 - copy'` (result from
47a1eee765/packages/payload/src/fields/setDefaultBeforeDuplicate.ts (L6))
Now, the `_order` value is re-calculated properly.
This improves performance when querying data in Postgers / SQLite with
`limit: 0`. Before, unless you additionally passed `pagination: false`
we executed additional count query to calculate the pagination. Now we
skip this as this is unnecessary since we can retrieve the count just
from `rows.length`.
This logic already existed in `db-mongodb` -
1b17df9e0b/packages/db-mongodb/src/find.ts (L114-L124)
Continuation of https://github.com/payloadcms/payload/pull/12265.
Currently, using `select` on new relationship virtual fields:
```
const doc = await payload.findByID({
collection: 'virtual-relations',
depth: 0,
id,
select: { postTitle: true },
})
```
doesn't work, because in order to calculate `post.title`, the `post`
field must be selected as well. This PR adds logic that sanitizes the
incoming `select` to include those relationships into `select` (that are
related to selected virtual fields)
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
The order of fields, when specified for the create export function was
not used for constructing the data. Now the fields order will be used.
### Why?
This is important to building CSV data for consumption in other systems.
### How?
Adds logic to handle ordering the field values assigned to the export
data prior to building the CSV.
### What?
It's impossible to create a user with special characters in their email
in Payload CMS 3.35.0.
The issue is that currently the regex looks like this:
...payload/packages/payload/src/fields/validations.ts (line 202-203):
const emailRegex =
/^(?!.*\.\.)[\w.%+-]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/i
This allows users that have the following characters in their email to
be created:
%, ., +, -
The regex needs to get updated to the following:
const emailRegex =
/^(?!.*\.\.)[\w!#$%&'*+/=?^{|}~.-]+@a-z0-9?(?:.a-z0-9?)*.[a-z]{2,}$/i`
This way all special characters `!#$%&'*+/=?^_{|}~.-`` are hereby OK to
have in the email.
I've added more test-cases to cover a couple of more scenarios in the
forked repo.
### Why?
The regex is missing some special characters that are allowed according
to standards.
### How?
* Go to the admin ui and try to create a user with any of the newly
added special characters meaning (!#$%&'*+/=?^_{|}~.-`)
* You should get a validation error. However with the addition of the
above code it should all check out.
Fixes #
https://github.com/payloadcms/payload/issues/12180
---------
Co-authored-by: Mattias Grenhall <mattias.grenhall@assaabloy.com>
Fixes#11628
PR #6389 caused bug #11628, which is a regression, as it had already
been fixed in #4441
It is likely that some things have changed because [Lexical had recently
made improvements](https://github.com/facebook/lexical/pull/7046) to
address selection normalization.
Although it wasn't necessary to resolve the issue, I added a
`NormalizeSelectionPlugin` to the editor, which makes selection handling
in the editor more robust.
I'm also adding a new collection to the Lexical test suite, intending it
to be used by default for most tests going forward. I've left an
explanatory comment on the dashboard.
___
Looking at #11628's video, it seems users also want to be able to
prevent the first paragraph from being empty. This makes sense to me, so
I think in another PR we could add a button at the top, just [like we
did at the bottom of the
editor](https://github.com/payloadcms/payload/pull/10530).
### What?
Improve performance of the relationship select options by reducing the
fetched documents to only necessary data.
### Why?
The relationship select only requires an ID and title. Fetching the
whole document instead leads to slow performance on collections with
large documents.
### How?
Add a select parameter to the query, the same way it is done in the
[WhereBuilder](https://github.com/payloadcms/payload/blob/main/packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx#L105-L107)
already.
### What?
Migrating configs that include environment specific options can cause
issues and confusion for users.
### How?
Adds new section to `database/migrations` docs to highlight potential
issues with environment-specific settings when generating and running
migrations and includes some recommendations for addressing these
issues.
Closes#12241
### What?
Using the `Copy To Locale` function causes validation errors on content
with `id` fields in postgres, since these should be unique.
```
key not found: error:valueMustBeUnique
key not found: error:followingFieldsInvalid
[13:11:29] ERROR: There was an error copying data from "en" to "de"
err: {
"type": "ValidationError",
"message": "error:followingFieldsInvalid id",
"stack":
ValidationError: error:followingFieldsInvalid id
```
### Why?
In `packages/ui/src/utilities/copyDataFromLocale.ts` we are passing all
data from `fromLocaleData` including the `id` fields, which causes
duplicates on fields with unique id's like `Blocks` and `Arrays`.
### How?
To resolve this i implemented a function that recursively remove any
`id` field on the passed data.
### Fixes
- https://github.com/payloadcms/payload/issues/10684
- https://discord.com/channels/967097582721572934/1351497930984521800
---------
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Perf improvements and reliability of document reindexing and
synchronization of plugin-search functions.
## What
Reindex Handler (generateReindexHandler.ts):
- Replaced `Promise.all` with sequential `await` to prevent transaction
issues.
- Added `depth: 0` to payload.find for lighter queries.
Sync Operations (syncDocAsSearchIndex.ts):
- Standardized depth: 0 across create, delete, update, and find API
calls.
- Streamlined conditionals for create operations.
## Why
Improved performance with reduced query overhead.
Enhanced transaction safety by avoiding parallel database operations.
GraphQL requests with join fields result in a lot of extra count rows
queries that aren't necessary. This turns off pagination and uses
limit+1 and slice instead.
### What?
This fixes an issue raised by @maximseshuk in this PR #11553. Here is
the text of the original comment:
If the field has the property hasMany: true and you select one item, it
shows up in the select field, but any additional selected items won't be
visible in the select field, even though the data is actually there and
can be saved. After refreshing the page, they appear.
In addition I added a fix to an issue where the filterOptions weren't
being passed in to the useListDrawer hook properly in polymorphic
relationships
### How?
Instead of using the push method to update the value state, a new array
is created and directly set using useState. I think the issue was
because using push mutates the original array.
### What?
So, while resetting the password using the Local API, I encountered a
validation error for localized fields. I jumped into the Payload
repository, and saw that `payload.update` is being used in the process,
with no locale specified/supported. This causes errors if the user has
localized fields, but specifying a locale for the password reset
operation would be silly, so I suggest turning this into a db operation,
just like the user fetching operation before.
### How?
I replaced this:
```TS
user = await payload.update({
id: user.id,
collection: collectionConfig.slug,
data: user,
req,
})
```
With this:
```TS
user = await payload.db.updateOne({
id: user.id,
collection: collectionConfig.slug,
data: user,
req,
})
```
So the validation of other fields would be skipped in this operation.
### Why?
This is the error I encountered while trying to reset password, it
blocks my project to go further :)
```bash
Error [ValidationError]: The following field is invalid: Data > Name
at async sendOfferEmail (src/collections/Offers/components/SendEmailButton/index.tsx:18:20)
16 | try {
17 | const payload = await getPayload({ config });
> 18 | const token = await payload.forgotPassword({
| ^
19 | collection: "offers",
20 | data: {
{
data: [Object],
isOperational: true,
isPublic: false,
status: 400,
[cause]: [Object]
}
cause:
{
id: '67f4c1df8aa60189df9bdf5c',
collection: 'offers',
errors: [
{
label: 'Data > Name',
message: 'This field is required.',
path: 'name'
}
],
global: undefined
}
```
P.S The name field is totally fine, it is required and filled with
values in both locales I use, in admin panel I can edit and save
everything without any issues.
<!--
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 #
-->
Fixes next build issue related to `cloudflare:sockets`.
Related Next.js discussion thread here:
https://github.com/vercel/next.js/discussions/50177
Commands to recreate issue locally
```sh
pnpm run script:pack --dest templates/with-postgres && \
pnpm run script:build-template-with-local-pkgs with-postgres postgresql://localhost:5432/payloadtests
```
**Build Error:**
```
Failed to compile.
cloudflare:sockets
Module build failed: UnhandledSchemeError: Reading from "cloudflare:sockets" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "cloudflare:" URIs.
at /home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:408376
at Hook.eval [as callAsync] (eval at create (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:6:1)
at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:14:6378)
at Object.processResource (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:408301)
at processResource (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:5308)
at iteratePitchingLoaders (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:4667)
at runLoaders (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:8590)
at NormalModule._doBuild (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:408163)
at NormalModule.build (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:410176)
at /home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:82494
```
<!--
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 a link to the Payload config in the query presets docs,
and adjusts the links to the edit view components in the collections and
global config pages.
### Why?
To direct users to the correct location.
### How?
Changes to a few docs.
Fixes#12199
### What?
As a native German speaker, I noticed some areas where the translations could be improved. While the ai translations are impressive, some of them are grammatically incorrect, inconsistent or just sound weird.
This PR improves them to provide a better experience for german users.
### What?
Enables the indent/outdent button if at least one selected node can be
indented/outdented.
### Why?
Before, the buttons were disabled e.g. if multiple nodes were selected
of which one was not indentable/outdentable or if a child node was not
indentable but the parent was, leading to inconsistent behavior.
### How?
Checks if the node itself or any parent fulfills the criteria. The
change affects only the buttons active state, not the actual indentation
logic.
Fixes https://github.com/payloadcms/payload/pull/12042
Fixes https://github.com/payloadcms/payload/issues/12090, in MongoDB the
documents are sorted by distance automatically whenever the `near`
operation is used, which we have in the docs:
> When querying using the near operator, the returned documents will be
sorted by nearest first.
This fixes this incosistensty between Postgres and MongoDB.
⚠️ This change potentially can cause to produce different results, if
you used the `near` operator without `sort: 'pointFieldName'`.
### What?
Resets the indentation on editor updates for nodes for which indentation
is disabled.
### Why?
If a node gets transformed, e.g. from a list to a paragraph node, it
remains the indent property by default. If indentation for this node is
disabled, it would remain indented although it shouldn't.
### How?
Adds a listener which resets the indent status on updates for
non-indentable nodes.
When doing `payload.db.queryDrafts` with `select` without `version`, or
simply your select looks like:
`select: { version: { nonExistingField: true } }` - the `queryDrafts`
function will crash because it tries to access the `version` field.
This PR adds a fallback.
This PR optimizes the new virtual fields with relationships feature
https://github.com/payloadcms/payload/pull/11805 when the path
references the ID field, for example:
```
{
name: 'postCategoryID',
type: 'number',
virtual: 'post.category.id',
},
```
Previously, we did additional population of `category`, which is
unnecessary as we can always grab the ID from the `category` value
itself. One less querying step.
The plugin-search collection uses an `afterDelete` hook to remove search
records from the database. Since a deleted document in postgres causes
cascade updates for the foreign key, the query for the document by
relationship was not returning the record to be deleted.
The solution was to change the delete hook to `beforeDelete` for the
search enabled collections. This way we purge records before the main
document so the search document query can find and delete the record as
expected.
An alternative solution in #9623 would remove the `req` so the delete
query could still find the document, however, this just works outside of
transactions which isn't desirable.
fixes https://github.com/payloadcms/payload/issues/9443
<!--
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 ensures defaultSort is reflected in join tables.
### Why?
Currently, default sort is not reflected in the join table state. The
data _is_ sorted correctly, but the table state sort is undefined. This
is mainly an issue for join fields with `orderable: true` because you
can't re-order the table until `order` is the selected sort column.
### How?
Added `defaultSort` prop to the `<ListQueryProvider />` in the
`<RelationshipTable />` and ensured the default state gets set in
`<ListQueryProvider />` when `modifySearchParams` is false.
**Before:**
<img width="1390" alt="Screenshot 2025-04-11 at 2 33 19 AM"
src="https://github.com/user-attachments/assets/4a008d98-d308-4397-a35a-69795e5a6070"
/>
**After:**
<img width="1362" alt="Screenshot 2025-04-11 at 3 04 07 AM"
src="https://github.com/user-attachments/assets/4748e354-36e4-451f-83e8-6f84fe58d5b5"
/>
Fixes#12083
---------
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
### What
This PR introduces a new `beforeDocumentControls` slot to the edit view
of both collections and globals.
It allows injecting one or more custom components next to the document
control buttons (e.g., Save, Publish, Save Draft) in the admin UI —
useful for adding context, additional buttons, or custom UI elements.
#### Usage
##### For collections:
```
admin: {
components: {
edit: {
beforeDocumentControls: ['/path/to/CustomComponent'],
},
},
},
```
##### For globals:
```
admin: {
components: {
elements: {
beforeDocumentControls: ['/path/to/CustomComponent'],
},
},
},
```
This adds a new `showSaveDraftButton` option to the
`versions.drafts.autosave` config for collections and globals.
By default, the "Save as draft" button is hidden when autosave is
enabled. This new option allows the button to remain visible for manual
saves while autosave is active.
Also updates the admin UI logic to conditionally render the button when
this flag is set, and updates the documentation with an example usage.