When using the `admin.hidden: true` property on a collection, it
rightfully removes all navigation and routing for that particular
collection. However, this also affects the expected behavior of hidden
entities when they are rendered within a drawer, such as the document
drawer or list drawer. For example, when creating a new _admin.hidden_
document through the relationship or join field, the drawer should still
render the view, despite the underlying route for that view being
disabled. This change was a result of the introduction of on-demand
server components in #8364, where we now make a server roundtrip to
render the view in its entirety, which include the logic that redirects
these hidden entities.
Now, we pass a new `overrideEntityVisibility` argument through the
server function that, when true, skips this step. This way documents can
continue to respect `admin.hidden` while also having the ability to
override on a case-by-case basis throughout the UI.
### What?
Adds ability to copy data from one locale to another at a document
level.
### How?
For any localized collection, you will find a new option in the document
controls called `Copy to Locale`.
This option will open a drawer, from here you can select your origin and
destination locales.
If data already exists in the destination locale, you can choose to:
1. Overwrite this data (this will copy any empty fields in your origin
locale)
2. Not overwrite existing data (this will only copy data into empty
fields in the destination locale)
---------
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
If you had a lot of fields and collections, createClientConfig would be
extremely slow, as it was copying a lot of memory. In my test config
with a lot of fields and collections, it took 4 seconds(!!).
And not only that, it also ran between every single page navigation.
This PR significantly speeds up the createClientConfig function. In my
test config, its execution speed went from 4 seconds to 50 ms.
Additionally, createClientConfig is now properly cached in both dev &
prod. It no longer runs between every single page navigation. Even if
you trigger a full page reload, createClientConfig will be cached and
not run again. Despite that, HMR remains fully-functional.
This will make payload feel noticeably faster for large configs -
especially if it contains a lot of richtext fields, as it was previously
deep-copying the relatively large richText editor configs over and over
again.
## Before - 40 sec navigation speed
https://github.com/user-attachments/assets/fe6b707a-459b-44c6-982a-b277f6cbb73f
## After - 1 sec navigation speed
https://github.com/user-attachments/assets/384fba63-dc32-4396-b3c2-0353fcac6639
## Todo
- [x] Implement ClientSchemaMap and cache it, to remove
createClientField call in our form state endpoint
- [x] Enable schemaMap caching for dev
- [x] Cache lexical clientField generation, or add it to the parent
clientConfig
## Lexical changes
Red: old / removed
Green: new

### Speed up version queries
This PR comes with performance optimizations for fetching versions
before a document is loaded. Not only does it use the new select API to
limit the fields it queries, it also completely skips a database query
if the current document is published.
### Speed up lexical init
Removes a bunch of unnecessary deep copying of lexical objects which
caused higher memory usage and slower load times. Additionally, the
lexical default config sanitization now happens less often.
Deprecates `react-animate-height` in favor of native CSS, specifically
the `interpolate-size: allow-keywords;` property which can be used to
animate to `height: auto`—the primary reason this package exists. This
is one less dependency in our `node_modules`. Tried to replicate the
current DOM structure, class names, and API of `react-animate-height`
for best compatibility.
Note that this CSS property is experimental BUT this PR includes a patch
for browsers without native support. Once full support is reached, the
patch can be safely removed.
The [previous fix](https://github.com/payloadcms/payload/pull/8735)
worked but was a breaking change because it set a `z-index` in the
`.react-select` wrapper instead of the `.rs__menu`, creating a new
stacking-context, therefore making any existing customizations to the
menu's `z-index` not work. This was a way to fix a regression introduced
by the css-layers, in which Payload's custom `z-index: 4` no longer took
precedence over react-select's default `z-index: 1`.
With this PR we remove the default `z-index: 1` applied by react-select,
so that the `z-index: 4` set in the "payload-default" css layer can take
effect. An alternative to this fix would be to use `z-index: 4
!important`, but this has the advantage of allowing the `z-index` to be
easily customized by the consumers of the CMS, as with all the other
styles.
### Screenshots

### What?
This log was appearing when the DatePicker loaded without a registered
locale:
```
A locale object was not found for the provided string ["enUS"].
```
Also fixes css misalignment of icons inside date picker field
### Why?
If i`18n.dateFNS` had not loaded, we were registering the locale with an
undefined value.
### How?
Only register the locale for react-datepicker if i18n.dateFNS is
present.
List preferences were improperly saving their own records onto
themselves when building table state through the server function. This
was happening because the entire preference document was being spread
onto the new preferences, as opposed to just the value itself:
```diff
const mergedPrefs = {
- ...(preferencesResult || {}),
+ ...(preferencesResult?.value || {}),
columns,
}
```
This PR also swaps `dequal` out for `dequal/lite`.
TS 5.7 added support for ES2024. By keeping target: “esnext”, we would
have accidentally set our minimum supported ES version to ES2024.
This sets it to ES2022, which is the version supported by Node 18
### What?
Non-standard ids caused an issue when finding the document on the
server.
This is an odd regression, in 2.0 we were fetching the document on the
client so the request would handle decoding the url. Now we are fetching
the document on the server and need to do this manually when reading id
from route params.
### Why?
The slug pulled out of the url for an id of `id 1` would equate to
`id%201` which would fail in the `payload.find` call since there is not
an id stored as `id%201` but instead `id 1`.
### How?
Wherever we are calling payload.find in the views and querying by `id`
it gets ran through a helper function that decodes it properly.
Fixes#9373
Fixes https://github.com/payloadcms/payload/issues/9363
This fixes the following issues that caused fields to be either hidden,
or incorrectly set to readOnly in certain configurations:
- In some cases, permissions were sanitized incorrectly. This PR
rewrites the sanitizePermissions function and adds new unit tests
- after a document save, the client was receiving unsanitized
permissions. Moving the sanitization logic to the endpoint fixes this
- Various incorrect handling of permissions in our form state endpoints
/ RenderFields
Closes#9242 and #9365. Autosave-enabled documents rendered within a
drawer were not being properly handled. This was causing multiple draft
documents to be created upon opening the drawer, as well as an empty
document returned from the server function, etc.
### What?
When a document is saved the data from useDocumentInfo was stale.
### Why?
Previously we would refresh the entire document by calling the
form-state endpoint, we no longer do that.
### How?
Adds a new variable accessible from useDocumentInfo,
`savedDocumentData`, that is updated when the document is successfully
saved and defaults to initialData.
Fixes#9264. When externally updating array or block rows through the
`addFieldRow` or `replaceFieldRow` methods, nested rich text fields
along with any custom components within them are never rendered. This is
because unless the form is explicitly set to modified, as the default
array and blocks fields currently do, the newly generated form-state
will skip the rendering step. Now, the underlying callbacks themselves
automatically set the form to modified to trigger rendering.
### What?
Could not finalize selection of `hasMany` uploads inside of the drawer.
### Why?
The Select component was not being rendered in the beforeActions prop of
the ListControls when row selections was enabled.
### How?
Renders the Select component when row selections are enabled and
onBulkSelect is present.
The biggest difference comes from calling `RenderServerComponent` as a
function, instead of rendering it by using `<RenderServerComponent`.
This gets rid of wasteful blocks of codes sent to the client that look
like this:

HTML size comparison:
## Admin test suite
| View | Before | After |
|------|---------|--------|
| Dashboard | 331 kB | 83 kB |
| collections/custom-views-one Edit | 285 kB | 76.6 kB |
## Fields test suite
| View | Before | After |
|------|---------|--------|
| collections/lexical Edit | 189 kB | 94.4 kB |
| collections/lexical List | 152 kB | 62.9 kB |
## Community test suite
| View | Before | After |
|------|---------|--------|
| Dashboard | 78.9 kB | 43.1 kB |
### What?
In the WhereBuilder Condition DefaultFilter component, there is a switch
statement that contains components to return based on the built filter
in the admin ui. Having a filter built out then navigating to another
collection list view causes an error to occur due to InternalField being
undefined but the DefaultFilter tries to access the field on it.
### Why?
To fix unexpected `cannot access property field of undefined` errors.
### How?
Adding a conditional chaining operator.
Odd thing here is that the `Text` component where this error originates
from doesn't actually make use of the passed `InternalField`. Might be
worth it to take a closer look at it.
Fixes#9179
Optimizes initial page responses by removing unnecessary inline field
styles that were being sent through the HTML response. The Client Config
contains a large number of duplicates of the string:
`"style\":{\"flex\":\"1 1 auto\"}`, one for every single field within
the entirely of the config. This leads to hundreds or potentially
thousands of instances of this same string, depending on the number of
fields within the config itself. This is regardless of custom field
widths being defined. Instead, we can do this entirely client-side,
preventing this string from ever being transmitted over the network in
the first place.
## Breaking Changes
This only effects those who are importing Payload's field components
into your own Custom Components or front-end application. The `width`
prop no longer exists. It has been consolidated into the existing
`style` prop. To migrate, simply move this prop as follows:
```diff
import { TextInput } from '@payloadcms/ui
export const MyCustomComponent = () => {
return (
<TextInput
- width="60%"
style={{
+ width: "60%,
}}
/>
)
}
```
Custom field description functions were being duplicately called in both
the Client Config and form state. Static field descriptions were also
being rendered in form state unnecessarily. Now, field description
functions are only executed once within form state, and static
descriptions are deferred to the client for rendering.
Supports bi-directional import/export between MDX <=> Lexical. JSX will
be mapped to lexical blocks back and forth.
This will allow editing our mdx docs in payload while keeping mdx as the
source of truth
---------
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
### What?
Fixes the issue when visiting the create view with the Join Field and
using postgres adapter
```
invalid input syntax for type integer: "NaN"
```
This happens because we don't have an ID yet and we send to the
database:
`WHERE id = NaN`
### How?
Avoids calling `getTableState` inside of `RelationshipTable` if there's
no ID yet, as it will always lead to the same empty result. While we
_could_ avoid error directly in the database adapter, I don't think we
should do that render request
Fixes https://github.com/payloadcms/payload/issues/9193
The field RSC now provides an initial state for all lexical blocks. This
completely obliterates any flashes and lexical block loading states when
loading or saving a document.
Previously, when a document is loaded or saved, every lexical block was
sending a network request in order to fetch their form state. Now, this
is batched and handled in the lexical server component. All lexical
block form states are sent to the client together with the parent
lexical field, and are thus available immediately.
We also do the same with block collapsed preferences. Thus, there are no
loading states or layout shifts/flashes of blocks anymore.
Additionally, when saving a document while your cursor is inside a
lexical field, the cursor position is preserved. Previously, a document
save would kick your cursor out of the lexical field.
## Look at how nice this is:
https://github.com/user-attachments/assets/21d736d4-8f80-4df0-a782-7509edd993da
**BREAKING:**
This removes the `feature.hooks.load` and `feature.hooks.save`
interfaces from custom lexical features, as they weren't used internally
and added unnecessary, additional overhead.
If you have custom features that use those, you can migrate to using
normal payload hooks that run on the server instead of the client.
With this PR, you can now customize the way that `blocks` and
`inlineBlocks` are rendered within Lexical's `BlocksFeature` by passing
your own React components.
This is super helpful when you need to create "previews" or more
accurate UI for your Lexical blocks.
For example, let's say you have a `gallery` block where your admins
select a bunch of images. By default, Lexical would just render a
collapsible with your block's fields in it. But now you can customize
the `admin.components.Block` property on your `block` config by passing
it a custom React component for us to render instead.
So using that, with this `gallery` example, you could make a dynamic
gallery React component that shows the images to your editors - and then
render our built-in `BlockEditButton` to allow your editors to manage
your gallery in a drawer.
Here is an example where the BlockEditButton is added to the default
Block Collapsible/Header:

---------
Co-authored-by: James <james@trbl.design>
Protects the `/api/access` endpoint behind authentication and sanitizes
the result, making it more secure and significantly smaller. To do this:
1. The `permission` keyword is completely omitted from the result
2. Only _truthy_ access results are returned
3. All nested permissions are consolidated when possible
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Co-authored-by: James <james@trbl.design>
**BREAKING:**
Improves type-safety of collection / global slugs by using `CollectionSlug` / `UploadCollectionSlug` and `GlobalSlug` types instead of `string` in these places:
Adds `UploadCollectionSlug` and `TypedUploadCollection` utility types
This also changes how we suggest to add an upload collection to a cloud-storage adapter:
Before:
```ts
azureStorage({
collections: {
[Media.slug]: true,
},
})
```
After:
```ts
azureStorage({
collections: {
media: true,
},
})
```
The collection list columns are stored as user preferences to the
payload-preferences collection. Normally one user should never have
duplicate documents with the same key. This is controlled by using an
upsert normally. The collection list does not have a good way to call
upsert and was creating preferences documents every time. This change
makes it so that existing preferences are updated rather than created
with each column change.