- reduces unnecessary shallow copying within operations by removing
unnecessary spreads or .map()'s
- removes unnecessary `deleteMany` call in `deleteUserPreferences` for
auth-enabled collections
- replaces all instances of `validOperators.includes` with
`validOperatorMap[]`. O(n) => O(1)
- optimizes the `sanitizeInternalFields` function. Previously, it was
doing a **lot** of shallow copying
Data for the EditMany view was fetched even though the EditMany Drawer
was not open. This, in combination with the router.replace call to add
the default limit query param, caused the root layout to re-render
This significantly optimizes the form state, reducing its size by up to
more than 3x and improving overall response times. This change also has
rolling effects on initial page size as well, where the initial state
for the entire form is sent through the request. To achieve this, we do
the following:
- Remove `$undefined` strings that are potentially attached to
properties like `value`, `initialValue`, `fieldSchema`, etc.
- Remove unnecessary properties like empty `errorPaths` arrays and empty
`customComponents` objects, which only need to exist if used
- Remove unnecessary properties like `valid`, `passesCondition`, etc.
which only need to be returned if explicitly `false`
- Remove unused properties like `isSidebar`, which simply don't need to
exist at all, as they can be easily calculated during render
## Results
The following results were gathered by booting up each test suite listed
below using the existing seed data, navigating to a document in the
relevant collection, then typing a single letter into the noted field in
order to invoke new form-state. The result is then saved to the file
system for comparison.
| Test Suite | Collection | Field | Before | After | Percentage Change |
|------|------|---------|--------|--------|--------|
| `field-perf` | `blocks-collection` | `layout.0.field1` | 227kB | 110
kB | ~52% smaller |
| `fields` | `array-fields` | `items.0.text` | 14 kB | 4 kB | ~72%
smaller |
| `fields` | `block-fields` | `blocks.0.richText` | 25 kB | 14 kB | ~44%
smaller |
After working on this I found a more accurate way to reproduce the bug:
- in the issue repro, type a letter in the select menu.
- delete the letter and wait for the debounce (300ms)
- type another letter.
- in devtools, you should see that the query increases the pagination by
+1. With this change, the pagination is reset when the input changes.
Fixes#10496
I'd like to do integration testing. But since there is no isolated `/ui`
test yet, this requires some planning. I have it pending.
### What?
Allows a user to delete a scheduled publish event after it has been
added:

### Why?
Previously a user had no control over making changes once scheduled.
### How?
Extends the `scheduledPublishHandler` server action to accept a
`deleteID` for the event that should be removed and exposes this to the
user via the admin UI in a new column in the Upcoming Events table.
Continuation of #10540. Passes server props to custom label components
rendered within table columns, such as the list view. This way custom
server components can have access to `payload`, `i18n`, etc. as
expected.
Since postgres uses number IDs by default, when we were storing the
relationship field value with postgres we weren't able to query it
This fixes that problem by casting the ID to always a string making it
safe for querying inside the JSON field
This PR adds `cacheTags: boolean` (default `true`) to allow users to
disable the appended document updatedAt value in the case of hosting
with third party CDNs which may not allow additional search params and
throw an error.
It also fixes how we append this value to consider the case where the
URL already contains parameters and appends it with `&` instead.
In the future `cacheTags` can be made an object to allow granularity for
disabling `eTag` headers used for caching as well.
The cache tag control should help with these two issues:
- Fixes https://github.com/payloadcms/payload/issues/9880
- Fixes https://github.com/payloadcms/payload/issues/9993
The appending of the value correctly addresses this:
- Fixes https://github.com/payloadcms/payload/issues/10139
Fixes https://github.com/payloadcms/payload/issues/10436
Fixes an issue where drafts' updatedAt timestamp is not being updated.
We weren't updating the `versionData` to have the right timestamp in the
saveVersion operation when drafts were being updated.
Added e2e tests to make sure 'Last Modified' is always different in both
autosave and non-autosave drafts.
Editing fields during a locale change on slow networks can lead to
changes being reset when the new form state is returned. This is because
the fields receive new values from context when the new locale loads in,
which may have occurred _after_ changes were made to the fields. The fix
is to subscribe to a new `localeIsLoading` context which is set
immediately after changing locales, and then reset once the new locale
loads in. This also removes the misleading `@deprecated` flag from the
`useLocale` hook itself.
The `ListPreferences` and `ColumnPreferences` types were defined
multiple times in different places, making it difficult to make changes
across the board. Now, the `ListPreferences` type is exported directly
from `payload` alongside the other preferences types, and
`ColumnPreferences` has been merged directly into this type to simplify
usage as this is not a standalone preference.
### What?
In the scheduled publish feature, it is possible to open the modal to
schedule publishing a document even before you have saved a draft. This
change removes the option to open the drawer to even attempt this.
This also fixes an issue if you do not have permission to publish, you
cannot schedule publish either.
### Why?
There were numerous problems:
1. The schedule publish events would show all other publish events for
the collection without an ID in the query
2. Scheduling a publish would not work without an ID for a document to
publish
### How?
Removes the Schedule Publish menu item and drawer if you are on a
collection without an ID or you do not have permission to publish.
Without an ID:

With an ID:

Perviously, you would always have the option to Schedule Publish.
### What?
The status indicator was not updating properly when users were clicking
"unpublish" and "revert changes"
### Why?
hasPublishedDoc, unpublishedVersionCount and
mostRecentVersionIsAutosaved states were not being updated properly.
### How?
This PR updates the variables when interacting with the status actions
and sets mostRecentVersionIsAutosaved to false when the publish button
is clicked.
Fixes https://github.com/payloadcms/payload/issues/9531
The localization e2e test is notorious for flaking, consuming a lot of
time and resources continually retrying. This was because the test was
attempting to click DOM elements using selectors that never resolve, or
attempting to click inaccessible DOM nodes such as those behind a modal.
The fix is to ensure that the dot nav, for example, is disabled while
form state loads, and that modals are properly closed prior to executing
subsequent tests, etc. Tests also needed to explicitly check for
_enabled_ states before performing click actions, rather than simply
awaiting their visibility.
Fixes#10018. When toggling columns, then sorting them, the table is
reset to the collection's default columns instead of the user's
preferred columns. This is because when sorting columns, a stale
client-side cache of the user's preferences is used to update their sort
preference. This is because when column state is constructed
server-side, it completely bypasses the client-side cache. To fix this,
sort preferences are now also set on the server right alongside column
preferences, which performs an upsert-like operation to ensure that no
existing preferences are lost.
To reproduce the bug:
1. Within a Lexical editor, insert a relationship field.
2. In the drawer, change the selected collection.
3. The table below changes correctly, but the title and the "create new"
button quickly revert to the original option.
https://github.com/user-attachments/assets/e4b7c615-4b98-4c11-a4b9-a828606edb6f
<!--
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 #
-->
Similar to #10331. Since React 19, refs can now be passed directly
through props without the need for `React.forwardRef`. This greatly
simplifies components types and overall syntax.
### What?
Previously, while uploading a file - if the uploading process took a bit
of time, users could still save the document prior to the upload fully
completing.
### Why?
During the uploading process - the save button should be disabled until
the upload is complete to prevent premature saving of an upload
document.
### How?
Now, we keep track of the state of the upload in a provider and disable
the save button until the file is fully uploaded.
Fixes an issue where if a checkbox field was in the first position of a
collection, and you tried to filter on it via the List view, the page
would crash.
Fixes#10234. Some fields, such as focal point fields for upload enabled
collections, were rendering in the condition selector despite being
hidden from the column selector. This was because the logic for the
column selector was filtering fields without labels, but the same was
not being done for the filter conditions. This, however, is not a good
way to filter these fields as it requires this specific logic to be
written in multiple places. Instead, they need to explicitly check for
`hidden` and `disabled` in addition to `disableListFilter` and
`disableListColumn`. The actual filtering logic has been improved across
the two instances as well, removing multiple duplicative loops.
This change has also exposed a underlying issue with the way columns
were handled within the table columns provider. When row selections were
enabled, the selector columns were present in column state. This caused
problems when interacting with column indices, such as when reordering
columns. Instead of needing to manually filter these out every time we
need to work with column state, they no longer appear there in the first
place. Instead, we inject the row selectors directly into the table
itself, completely isolating these row selectors from the column state.
What?
This PR fixes an issue with the WhereBuilder where if the first field in
a collection had disableListFilter enabled, the select in that fields
Condition would be rendered disabled, making it impossible to query docs
in list view.
Why?
To allow users to query their documents while still being able to set
disableListFilter on fields regardless of where they are in the
collection hierarchy.
How?
By setting the intitial field selection to the first
`admin.listDisabledColumn: false` field when clicking the Add Condition
button in the WhereBuilder and Condition components.
Fixes https://github.com/payloadcms/payload/issues/10110
Adds a feature to allow editors to schedule publish / unpublish events
in the future. Must be enabled by setting
`versions.drafts.schedulePublish: true` in your Collection / Global
configs.
https://github.com/user-attachments/assets/ca1d7a8b-946a-4eac-b911-c2177dbe3b1c
Todo:
- [x] Translate new i18n keys
- [x] Wire up locale-specific scheduled publish / unpublish actions
<!--
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 multiple `upload` fields would sequentially
overwrite the `BulkUpload` internal `onSuccess` function causing new
uploads to populate the incorrect field from which the interaction
started.
### Why?
Sequential `upload` fields use a `useEffect` to set the success function
of the `BulkUpload` provider component, however this did not take into
account many `upload` fields in a single document. This PR prevents many
`upload` fields from overriding their sibling's `onSuccess` function in
order to populate those fields correctly.
### How?
By changing the way the bulk upload component handles success functions
from a singular function to a map of functions based on a string path of
the field, or if necessary, using a collection slug in the case of a
bulk upload on an `upload` collection list view.
Fixes#10177
Before (One hasMany, one single):
[Editing-hasmany-single--Post-before--Payload.webm](https://github.com/user-attachments/assets/01aeaa64-a065-4e66-8ab4-6bb9d4fa8556)
Before (Many hasMany):
[Editing-hasmany-two--Post-before--Payload.webm](https://github.com/user-attachments/assets/a65c58aa-9a15-4cca-b2c4-17484c020ddc)
After (One hasMany, one single):
[Editing-hasmany-single--Post-after--Payload.webm](https://github.com/user-attachments/assets/7206f94e-4ce2-41b3-8b45-625f4974d28d)
After (Many hasMany):
[Editing-hasmany-two--Post-after--Payload.webm](https://github.com/user-attachments/assets/72dbbdee-d4a5-4488-8ef0-3dd3918115a9)
Fixes#10070. Adding new blocks or array rows can randomly get stuck
within an infinite loading state. This was because the abort controllers
responsible for disregarding duplicate `onChange` and `onSave` events
was not properly resetting its refs across invocations. This caused
subsequent event handlers to incorrectly abort themselves, leading to
unresolved requests and a `null` form state. Similarly, the cleanup
effects responsible for aborting these requests on component unmount
were also referencing its `current` property directly off the refs,
which can possible be stale if not first set as a variable outside the
return function.
This PR also carries over some missing `onSave` logic from the default
edit view into the live preview view. In the future the logic between
these two views should be standardized, as they're nearly identical but
often become out of sync. This can likely be done through the use of
reusable hooks, such as `useOnSave`, `useOnChange`, etc. Same with the
document locking functionality which is complex and deeply integrated
into each of these views.
<!--
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 deleting an entry in a `Join` via the
`Drawer` accessed through the `DrawerLink` would not update the table
until the page was refreshed.
### Why?
For a better, more reactive, deletion experience for end-users. Ideally,
the deletion is reflected in the table right away.
### How?
By passing an `onDrawerDelete` function to the `DrawerLink` which simply
filters out the existing doc according to an id.
Fixes#9580
Before:
[Editing---Post--before-Payload.webm](https://github.com/user-attachments/assets/3dd4df78-bb63-46b1-bf5f-7643935e15ad)
After:
[Editing---Post--after-Payload.webm](https://github.com/user-attachments/assets/97bb604f-41df-4cc9-8c46-9a59a19c72b7)
<!--
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 unpublish modal was unreachable due to
the high `z-index` on `Drawer` components. This makes unpublishing
documents from a drawer impossible. For example, when editting a
document from the drawer opened in a `RelationshipTable`.
### Why?
To allow editors to be able to unpublish docs regardless of drawer depth
and context.
### How?
By rendering the unpublish modal at a sufficiently high z-index, while
taking into account edit depth.
Fixes#10108
Before:
[Dashboard-unpublish-before--Payload.webm](https://github.com/user-attachments/assets/7acf1002-138e-48bd-81ec-76f5eabfb2d4)
After:
[Dashboard-unpublish-after--Payload.webm](https://github.com/user-attachments/assets/ff109ee9-5b63-43d0-931f-500ded8f6d3a)
### What?
The join field had a limitation imposed that prevents it from targeting
polymorphic relationship fields. With this change we can support any
relationship fields.
### Why?
Improves the functionality of join field.
### How?
Extended the database adapters and removed the config sanitization that
would throw an error when polymorphic relationships were used.
Fixes #
Add the ability to specify which columns should appear in the
relationship table of a join fields
The new property is in the Join field `admin.defaultColumns` and can be
set to an array of strings containing the field names in the desired
order.
### What?
The `readOnly` prop was not being passed down to the `email` &
`username` auth fields.
Resulting in these fields not being disabled properly if `update` access
was restricted.
### How?
Passes the `readOnly` prop through to the fields and now properly
disables these fields if `update` access is restricted.
Previously, Autosave could trigger 2 parallel fetches where the second
could outpace the first, leading to inconsistent results.
Now, we use a simple queue-based system where we can push multiple
autosave events into a queue, and only the latest autosave will be
performed.
This also prevents multiple autosaves from ever running in parallel.
Continuation of #9846 and partial fix for #9774. When setting
`admin.disableListFilter` retroactively, it remains active within the
list filter controls. Same for when the URL search query contains one of
these fields, except this will actually display the _wrong_ field,
falling back to the _first_ field from the config. The fix is to
properly disable the condition for this field if it's an active filter,
while still preventing it from ever rendering as an option within the
field selector itself.
Partial fix for #9774. When `admin.disableListColumn` is set
retroactively, it continues to appear in column state, but shouldn't.
This was because the table column context was not refreshing after HMR
runs, and would instead hold onto these stale columns until the page
itself refreshes. Similarly, this was also a problem when the user had
saved any of these columns to their list preferences, where those prefs
would take precedence despite these properties being set on the
underlying fields. The fix is to filter these columns from all requests
that send them, and ensure local component state properly refreshes
itself.
### What?
It became possible for fields to reset to a defined `defaultValue` when
bulk editing from the `edit-many` drawer.
### Why?
The form-state of all fields were being considered during a bulk edit -
this also meant using their initial states - this meant any fields with
default values or nested fields (`arrays`) would be overwritten with
their initial states
I.e. empty values or default values.
### How?
Now - we only send through the form data of the fields specifically
being edited in the edit-many drawer and ignore all other fields.
Leaving all other fields stay their current values.
Fixes#9590
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
Previously, `initCollapsed: true` `array` fields would auto collapse
when typing in their respective inputs while in the create new view.
### Why?
This was due to the fact that we were only checking if `preferences`
existed in `form state` to handle the current state of the array row and
then falling back on the `initCollapsed` prop if `preferences` didn't
exist.
This was a problem because during create - `preferences` do not exist
yet. As a result, the state of the array row would keep falling back to
collapsed if `initCollapsed` was set to `true`.
### How?
To fix this, we now check the actual form state first before falling
back to preferences and then falling back to the initCollapsed prop
value.
Fixes#9775
The join field was not respecting the defaultSort or defaultLimit of the
field configuration.
### Why?
This was never implemented.
### How?
This fix applies these correct limit and sort properties to the query,
first based on the field config and as a fallback, the collection
configuration.
### What?
Fixes issue with stale locale from searchParams
### Why?
Bad use of useEffect/useState inside our useSearchParams provider.
### How?
Memoize the locale instead of relying on the useEffect which was causing
unnecessary renders with stale values.
- [fix: join field shows loading when creating a
document](9f7a2e7936)
- [fix: join field
descriptions](90e8cdb464)
- [feat(ui): adds before & after inputs to join
field](19d43329ad)
---------
Co-authored-by: Patrik <patrik@payloadcms.com>