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.
Follow up to #11900. The `@payloadcms/next/auth` export was missing from
the published package.json because it was excluded from the
`publishConfig` property.
This PR adds an ability to specify a virtual field in this way
```js
{
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
],
},
{
slug: 'virtual-relations',
fields: [
{
name: 'postTitle',
type: 'text',
virtual: 'post.title',
},
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
},
],
},
```
Then, every time you query `virtual-relations`, `postTitle` will be
automatically populated (even if using `depth: 0`) on the db level. This
field also, unlike `virtual: true` is available for querying / sorting /
`useAsTitle`.
Also, the field can be deeply nested to 2 or more relationships, for
example:
```
{
name: 'postCategoryTitle',
type: 'text',
virtual: 'post.category.title',
},
```
Where the current collection has `post` - a relationship to `posts`, the
collection `posts` has `category` that's a relationship to `categories`
and finally `categories` has `title`.
This PR updates the field `condition` function property to include a new
`operation` argument.
The `operation` arg provides a string relating to which operation the
field type is currently executing within.
#### Changes:
- Added `operation: Operation` in the Condition type.
- Updated relevant condition checks to ensure correct parameter usage.
Removes `$undefined` strings from being sent through the network when
sending form state requests. When adding new array rows, we assign
`blockType: undefined` which is stringified to `"$undefined"`. This is
unnecessary, as simply not sending this property is equivalent, and this
is only a requirement for blocks. This change will save on request size,
albeit minimal.
| Before | After |
|--|--|
|<img width="1267" alt="Untitled"
src="https://github.com/user-attachments/assets/699f38bd-7db9-4a52-931d-084b8af8530f"
/> | <img width="1285" alt="image"
src="https://github.com/user-attachments/assets/986ecd4c-f22d-4143-ad38-0c5f52439c67"
/> |
### What?
Adjusts the `ChevronIcon` component to match the sizing of other icons
in the `ui` package. Also adds various styling adjustments to places
where icons are used.
### Why?
Using the `ChevronIcon` in other elements currently requires different
styling to make it consistent with other icons. This will make it so
that any usage of the any icons is consistent across components.
### How?
Resizes the `ChevronIcon` components and updates styling throughout the
admin panel.
<!--
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?
This PR fixes the config example in the block field page.
### Why?
The syntax was incorrect
### How?
Missing object property
Returning a boolean value from a constraint-level access control
function does nothing. For example:
```ts
{
label: 'Noone',
value: 'noone',
access: () => false,
},
```
This is because we were only handling query objects, disregarding any
boolean values. The fix is to check if the query is a boolean, and if
so, format a query object to return.
When server rendering custom components within form state, those
components receive a path that is correct at render time, but
potentially stale after manipulating array and blocks rows. This causes
the field to briefly render incorrect values while the form state
request is in flight.
The reason for this is that paths are passed as a prop statically into
those components. Then when we manipulate rows, form state is modified,
potentially changing field paths. The component's `path` prop, however,
hasn't changed. This means it temporarily points to the wrong field in
form state, rendering the data of another row until the server responds
with a freshly rendered component.
This is not an issue with default Payload fields as they are rendered on
the client and can be passed dynamic props.
This is only an issue within custom server components, including rich
text fields which are treated as custom components. Since they are
rendered on the server and passed to the client, props are inaccessible
after render.
The fix for this is to provide paths dynamically through context. This
way as we make changes to form state, there is a mechanism in which
server components can receive the updated path without waiting on its
props to update.
Re-ordering documents with drafts uses `payload.update()` with `select:
{ id: true }` and that causes draft versions of those docs to be updated
without any data. I've removed the `select` optimization to prevent data
loss.
Fixes#12097
### What?
Allows to indent children in richtext-lexical if the parent of that
child is not indentable. Changes the behavior introduced in
https://github.com/payloadcms/payload/pull/11739
### Why?
If there is a document structure with e.g. `tableNode > list > listItem`
and indentation of `tableNode` is disabled, it should still be possible
to indent the list items.
### How?
Disable the indent button only if indentation of one of the selected
nodes itself is disabled.
### What?
Converts numbers passed to a text field to avoid the database/drizzle
from converting it incorrectly.
### Why?
If you have a hook that passes a value to another field you can
experience this problem where drizzle converts a number value for a text
field to a floating point number in sqlite for example.
### How?
Adds logic to `transform/write/traverseFields.ts` to cast text field
values to string.
### What?
This PR adds the ability to use the ListDrawer component for selecting
related collections for the relationship field instead of the default
drop down interface. This exposes the advanced filtering options that
the list view provides and provides a good interface for searching for
the correct relationship when the workflows may be more complicated.
I've added an additional "selectionType" prop to the relationship field
admin config that defaults to "dropdown" for compatability with the
existing implementation but "drawer" can be passed in as well which
enables using the ListDrawer for selecting related components.
### Why?
Adding the ability to search through the list view enables advanced
workflows or handles edge cases when just using the useAsTitle may not
be informative enough to find the related record that the user wants.
For example, if we have a collection of oscars nominations and are
trying to relate the nomination to the person who recieved the
nomination there may be multiple actors with the same name (Michelle
Williams, for example:
[https://www.imdb.com/name/nm0931329/](https://www.imdb.com/name/nm0931329/),
[https://www.imdb.com/name/nm0931332/](https://www.imdb.com/name/nm0931332/)).
It would be hard to search through the current dropdown ui to choose the
correct person, but in the list view the user could use other fields to
identify the correct person such as an imdb id, description, or anything
else they have in the collection for that person. Other advanced
workflows could be if there are multiple versions of a record in a
collection and the user wants to select the most recent one or just
anything where the user needs to see more details about the record that
they are setting up the relationship to.
### How?
This implementation just re-uses the useListDrawer hook and the
ListDrawer component so the code changes are pretty minimal. The main
change is a new onListSelect handler that gets passed into the
ListDrawer and handles updating the value in the field when a record is
selected in the ListDrawer.
There were also a two things that I didn't implement as they would
require broader code changes 1) Bulk select from the ListDrawer when a
relationship is hasMany - when using bulkSelect in the list drawer the
relatedCollection doesn't get returned so this doesn't work for
polymorphic relationships. Updating this would involve changing the
useListDrawer hook 2) Hide values that are already selected from the
ListDrawer - potentially possible by modifying the filterOptions and
passing in an additional filter but honestly it may not be desired
behaviour to hide values from the ListDrawer as this could be confusing
for the user if they don't see records that they are expected to see
(maybe if anything make them unselectable and indicate that they are
disabled). Currently if an already selected value gets selected the
selected value gets replaced by the new value
https://github.com/user-attachments/assets/fee164da-4270-4612-9304-73ccf34ccf69
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
### What?
The default value is hardcoded instead of respecting the value filled in
the form setting
Fixes #
pass it down from props
Co-authored-by: Pan <kpkong@hk01.com>
### What?
There are some missing translations in Catalan, both related to the word
Collections, which in Catalan is "Col·leccions".
### Why?
To contribute to the Catalan language as a developer and native speaker
;)
### How?
Updated the wording in the `ca.ts` translations object, also removed
`catalan` from `not implemented languages` comment
### What?
Respects the defaultDepth setting in the admin UI API view.
### Why?
The current default is hardcoded to `1` with no configuration option.
This can lead to performance issues on documents with a lot of related
large documents. Having the ability to define a different default can
prevent this issue.
### How?
Set the depth in the API view to `config.defaultDepth` as default.
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
### What
Adds exportable server functions for `login`, `logout` and `refresh`
that are fully typed and ready to use.
### Why
Creating server functions for these auth operations require the
developer to manually set and handle the cookies / auth JWT. This can be
a complex and involved process - instead we want to provide an option
that will handle the cookies internally and simplify the process for the
user.
### How
Three re-usable functions can be exported from
`@payload/next/server-functions`:
- login
- logout
- refresh
Examples of how to use these functions will be added to the docs
shortly, along with more in-depth info on server functions.
Previously when the port number was bumped up (eg `3001`) in our dev
suite, HMR wouldn't work because it couldn't reliably read the new used
port and it would default to `3000`.
This assigns it properly to the env var and fixes that issues so HMR in
our dev suite works on other ports too.
Testing steps:
- Have a local instance of dev suite running already on port 3000
- New repo run dev, it will bump to `3001`
- Make any config change and you will see that HMR does not work without
this fix
Fixes an issue where an autosave being triggered would turn off the
ability to schedule a publish. This happened because we check against
`modified` on the form but with autosave modified is always true.
Now we make an exception for autosave enabled collections when checking
the modified state.
### What?
`OrderableTable` doesn't respect a user-sepcified `routes.api` value and
instead uses the default `/api`
### Why?
See #12080
### How?
Gets `config` via `useConfig`, and uses `config.routes.api` in the
`fetch` for reordering.
Fixes#12080
### What?
This PR corrects a typo in an error message and removes a console.log from the `orderBeforeChangeHook` hook.
### Why?
An error message contains a typo, and every time I reorder an orderable collection, `do not enter` gets logged.
<img width="153" alt="Screenshot 2025-04-11 at 1 11 29 AM" src="https://github.com/user-attachments/assets/13ae106b-0bb9-4421-9083-330d3b6f356d" />
following changes made by Commit a6f7ef8
> feat(db-*): export types from main export (#11914)
In 3.0, we made the decision to export all types from the main package
export (e.g. `payload/types` => `payload`). This improves type
discoverability by IDEs and simplifies importing types.
> This PR does the same for our db adapters, which still have a separate
`/types` subpath export. While those are kept for
backwards-compatibility, we can remove them in 4.0.
a6f7ef837a
the script responsible for generating file generated-schema.ts was not
updated to reflect this change in export paths
drizzle/src/utilities/createSchemaGenerator.ts
CURRENT
```typescript
const finalDeclaration = `
declare module '${this.packageName}/types' {
export interface GeneratedDatabaseSchema {
schema: DatabaseSchema
}
}
```
AFTER THIS PULL REQUEST
```typescript
const finalDeclaration = `
declare module '${this.packageName}' {
export interface GeneratedDatabaseSchema {
schema: DatabaseSchema
}
}
```
this pull request fixes the generation of generated-schema.ts avoiding
errors while building for production with command
```bash
npm run build
```

Lexical tests comprise almost half of the collections in the fields
suite, and are starting to become complex to manage.
They are sometimes related to other auxiliary collections, so
refactoring one test sometimes breaks another, seemingly unrelated one.
In addition, the fields suite is very large, taking a long time to
compile. This will make it faster.
Some ideas for future refactorings:
- 3 main collections: defaultFeatures, fully featured, and legacy.
Legacy is the current one that has multiple editors and could later be
migrated to the first two.
- Avoid collections with more than 1 editor.
- Create reseed buttons to restore the editor to certain states, to
avoid a proliferation of collections and documents.
- Reduce the complexity of the three auxiliary collections (text, array,
upload), which are rarely or never used and have many fields designed
for tests in the fields suite.
### What?
This PR adds support for `where` querying by the join field (don't
confuse with `where` querying of related docs via `joins.where`)
Previously, this didn't work:
```
const categories = await payload.find({
collection: 'categories',
where: { 'relatedPosts.title': { equals: 'my-title' } },
})
```
### Why?
This is crucial for bi-directional relationships, can be used for access
control.
### How?
Implements `where` handling for join fields the same as we do for
relationships. In MongoDB it's not as efficient as it can be, the old PR
that improves it and can be updated later is here
https://github.com/payloadcms/payload/pull/8858
Fixes https://github.com/payloadcms/payload/discussions/9683
Updated the readmes on our blank template so it's closer to what we have
on the website
template.
Updated the Vercel variation ones as well because those are used
directly for the Vercel marketplace.
This fixes an issue where fields with the name `file` was being
serialized as a top-level field in multipart form data even when the
collection was not upload-enabled. This caused the value of `file` (when
used as a regular field like a text, array, etc.) to be stripped from
the `_payload`.
- Updated `createFormData` to only delete `data.file` and serialize it
at the top level if `docConfig.upload` is defined.
- This prevents unintended loss of `file` field values for non-upload
collections.
The `file` field now remains safely nested in `_payload` unless it's
part of an upload-enabled collection.
This PR adds a new `SchedulePublish` config type on our schedulePublish
configuration in versions from being just boolean.
Two new options are supported:
- `timeFormat` which controls the formatting of the time slots, allowing
users to change from a 12-hour clock to a 24-hour clock (default to 12
hour)
- `timeIntervals` which controls the generated time slots (default 5)
Example configuration:
```
versions: {
drafts: {
schedulePublish: {
timeFormat: 'HH:mm',
timeIntervals: 5,
},
},
},
```
Fixes form state race conditions. Modifying state while a request is in
flight or while the response is being processed could result in those
changes being overridden.
This was happening for a few reasons:
1. Our merge logic was incorrect. We were disregarding local changes to
state that may have occurred while form state requests are pending. This
was because we were iterating over local state, then while building up
new state, we were ignoring any fields that did not exist in the server
response, like this:
```ts
for (const [path, newFieldState] of Object.entries(existingState)) {
if (!incomingState[path]) {
continue
}
// ...
}
```
To fix this, we need to use local state as the source of truth. Then
when the server state arrives, we need to iterate over _that_. If a
field matches in local state, merge in any new properties. This will
ensure all changes to the underlying state are preserved, including any
potential addition or deletions.
However, this logic breaks down if the server might have created _new_
fields, like when populating array rows. This means they, too, would be
ignored. To get around this, there is a new `addedByServer` property
that flags new fields to ensure they are kept.
This new merge strategy also saves an additional loop over form state.
1. We were merging form state based on a mutable ref. This meant that
changes made within one action cause concurrent actions to have dirty
reads. The fix for this is to merge in an isolated manner by copying
state. This will remove any object references. It is generally not good
practice to mutate state without setting it, anyways, as this causes
mismatches between what is rendered and what is in memory.
1. We were merging server form state directly within an effect, then
replacing state entirely. This meant that if another action took place
at the exact moment in time _after_ merge but _before_ dispatch, the
results of that other action would be completely overridden. The fix for
this is to perform the merge within the reducer itself. This will ensure
that we are working with a trustworthy snapshot of state at the exact
moment in time that the action was invoked, and that React can properly
queue the event within its lifecycle.
### What?
The `in` & `not_in` operators were not properly working for `text`
fields as this operator requires an array of values for it's input.
### How?
Conditionally renders a multi select input for `text` fields when
filtering by `in` & `not_in` operators.
Trying to understand why bug #12002 arose, I found that both the `sort`
and `hooks` test suites are not running in CI.
I'm adding those 2 suites to the array, though later we should find a
way to automate this so it doesn't happen again. Manually rewriting all
test suites in the GitHub action is error-prone. It's very easy to
forget to add it when creating a new test suite
### What?
In the List View, row data related to images and relationships gets
stuck when you go from one page to another.
### Why?
The `key` we are providing is not unique and not triggering the DOM to
update.
### How?
Uses the `row id` as a unique key prop to each table row to ensure
proper re-rendering of rows during pagination.
#### Testing
Adds e2e test to `upload` test suite. You can recreate the issue using
the `upload` test suite and new `list view preview` collection.
On devices without a top-notch CPU, typing in the rich text editor is
laggy even in the very basic community test suite's "Post" collection.
Lags can be up to multiple seconds. This lag can be reproduced by e.g.
throttling the CPU by 6x on a MacBook Pro with M1 Pro chip and 32GB of
RAM. Typing at regular speed already stutters, and the Chromium
performance monitor shows 100% peak CPU utilization. Under the same
circumstances, the Lexical rich text editor on
https://playground.lexical.dev/ does not exhibit the same laggy UI
reactions.
The issue was narrowed down to the editor state serialization that was
so far executed on every change in `Field.tsx` and utilizing more than 1
frame's worth of CPU time.
This PR attempts to address the issue by asking the browser to queue the
work in moments where it doesn't interfere with UI responsiveness, via
`requestIdleCallback`.
To verify this change, simulate a slow CPU by setting `CPU: 6x slowdown`
in the Chromium `Performance` Dev Tool panel, and then type into the
community test suite's example post's rich text field.
I did not collect exhaustive benchmarks, since numbers are system
specific and the impact of the code change is simple to verify.
Demos:
Before, whole words are not appearing while typing, but then appear all
at once, INP is 6s, and CPU at 100% basically the whole interaction
time:
https://github.com/user-attachments/assets/535653d5-c9e6-4189-a0e0-f71d39c43c31
After: Most letters appear without delay, individual letters can be
slightly delayed, but INP is much more reasonable 350ms, and CPU has
enough bandwidth to drop below 100% utilization:
https://github.com/user-attachments/assets/e627bf50-b441-41de-b3a3-7ba5443ff049⬆️ This recording is from an earlier solution attempt with 500ms
debouncing. The current approach with `requestIdleCallback` increases
CPU usage back to a close 100%, but the INP is further reduced to 2xxms
on my machine, and the perceived UI laggyness is comparable to this
recording.
---
This PR only addresses the rich text editor, because that's where the
performance was a severe usability deal-breaker for real world usage.
Presumably other input fields where users trigger a lot of change events
in succession such as text, textarea, number, and JSON fields might also
benefit from similar debouncing.