Fixes https://github.com/payloadcms/payload/issues/11568
### What? Out of sync errors states
- Collaspibles & Tabs were not reporting accurate child error counts
- Arrays could get into a state where they would not update their error
states
- Slight issue with toasts
### Tabs & Collapsibles
The logic for determining matching field paths was not functioning as
intended. Fields were attempting to match with paths such as `_index-0`
which will not work.
### Arrays
The form state was not updating when the server sent back errorPaths.
This PR adds `errorPaths` to `serverPropsToAccept`.
### Toasts
Some toasts could report errors in the form of `my > > error`. This
ensures they will be `my > error`
### Misc
Removes 2 files that were not in use:
- `getFieldStateFromPaths.ts`
- `getNestedFieldState.ts`
The Payload Admin Bar is now maintained in core and released under the
`@payloadcms` scope thanks to #3684. All templates and examples that
rely on this package now install from here and have been migrated
accordingly.
This change makes so that data that exists in MongoDB but isn't defined
in the Payload config won't be included to `payload.find` /
`payload.db.find` calls. Now we strip all the additional keys.
Consider you have a field named `secretField` that's also `hidden: true`
(or `read: () => false`) that contains some sensitive data. Then you
removed this field from the database and as for now with the MongoDB
adapter this field will be included to the Local API / REST API results
without any consideration, as Payload doesn't know about it anymore.
This also fixes https://github.com/payloadcms/payload/issues/11542 if
you removed / renamed a relationship field from the schema, Payload
won't sanitize ObjectIDs back to strings anymore.
Ideally you should create a migration script that completely removes the
deleted field from the database with `$unset`, but people rarely do
this.
If you still need to keep those fields to the result, this PR allows you
to do this with the new `allowAdditionalKeys: true` flag.
- Adds support for numeric canary versions ie. `3.28.0-canary.0`,
subsequent prereleases will increment accordingly (like Next.js)
- _Our old way of doing canary releases_ is still available but will now
be tagged as `internal` ex. `3.28.0-internal.shorthash`
- Releases are triggered via workflow dispatch in Actions. Triggers off
of main will be released as `canary`, all others will be `internal`.
This adds a new `disableAutoLinks` property to the `LinkFeature` that lets you disable the automatic creation of links while typing them in the editor or pasting them.
Deprecates the old HTML converter and introduces a new one that functions similarly to our Lexical => JSX converter.
The old converter had the following limitations:
- It imported the entire lexical bundle
- It was challenging to implement. The sanitized lexical editor config had to be passed in as an argument, which was difficult to obtain
- It only worked on the server
This new HTML converter is lightweight, user-friendly, and works on both server and client. Instead of retrieving HTML converters from the editor config, they can be explicitly provided to the converter function.
By default, the converter expects populated data to function properly. If you need to use unpopulated data (e.g., when running it from a hook), you also have the option to use the async HTML converter, exported from `@payloadcms/richtext-lexical/html-async`, and provide a `populate` function - this function will then be used to dynamically populate nodes during the conversion process.
## Example 1 - generating HTML in your frontend
```tsx
'use client'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'
import React from 'react'
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
const html = convertLexicalToHTML({ data })
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
## Example - converting Lexical Blocks
```tsx
'use client'
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
import type {
DefaultNodeTypes,
SerializedBlockNode,
SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import {
convertLexicalToHTML,
type HTMLConvertersFunction,
} from '@payloadcms/richtext-lexical/html'
import React from 'react'
type NodeTypes =
| DefaultNodeTypes
| SerializedBlockNode<MyTextBlock>
| SerializedInlineBlockNode<MyInlineBlock>
const htmlConverters: HTMLConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
...defaultConverters,
blocks: {
// Each key should match your block's slug
myTextBlock: ({ node, providedCSSString }) =>
`<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
},
inlineBlocks: {
// Each key should match your inline block's slug
myInlineBlock: ({ node, providedStyleTag }) =>
`<span${providedStyleTag}>${node.fields.text}</span$>`,
},
})
export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
const html = convertLexicalToHTML({
converters: htmlConverters,
data,
})
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
## Example 3 - outputting HTML from the collection
```ts
import type { HTMLConvertersFunction } from '@payloadcms/richtext-lexical/html'
import type { MyTextBlock } from '@/payload-types.js'
import type { CollectionConfig } from 'payload'
import {
BlocksFeature,
type DefaultNodeTypes,
lexicalEditor,
lexicalHTMLField,
type SerializedBlockNode,
} from '@payloadcms/richtext-lexical'
const Pages: CollectionConfig = {
slug: 'pages',
fields: [
{
name: 'nameOfYourRichTextField',
type: 'richText',
editor: lexicalEditor(),
},
lexicalHTMLField({
htmlFieldName: 'nameOfYourRichTextField_html',
lexicalFieldName: 'nameOfYourRichTextField',
}),
{
name: 'customRichText',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: [
{
interfaceName: 'MyTextBlock',
slug: 'myTextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
}),
],
}),
},
lexicalHTMLField({
htmlFieldName: 'customRichText_html',
lexicalFieldName: 'customRichText',
// can pass in additional converters or override default ones
converters: (({ defaultConverters }) => ({
...defaultConverters,
blocks: {
myTextBlock: ({ node, providedCSSString }) =>
`<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
},
})) as HTMLConvertersFunction<DefaultNodeTypes | SerializedBlockNode<MyTextBlock>>,
}),
],
}
```
https://github.com/payloadcms/payload/pull/10794 added new defaults the config - however, these were only added to the deprecated `defaults` object, which caused our CI to fail. This PR adds them to the new `addDefaultsToConfig` function
This PR adds a top-level `auth` property to the Payload config, where
you can specify a new `jwtOrder` property to dictate, in Payload's local
auth strategy, which JWT extraction methods should be leveraged, and in
which order.
For example, we currently use incoming request headers to retrieve a JWT
in the following order:
1. If there is an `Authorization: JWT ${token}` header
2. If there is an `Authorization: Bearer ${token}` header
3. If there is an HTTP-only cookie with a token present
Now you can define which of these strategies you'd like to support, and
in which order.
Todo:
- [ ] Docs
- [ ] Tests
Previously when `authenticate` method from an authentication strategy
failed it stopped execution of the current request in
`createPayloadRequest` which isn't a good behavior.
Right now it completely prevents the admin panel from loading:
<img width="637" alt="image"
src="https://github.com/user-attachments/assets/7a6ca006-7457-4f9f-8746-7b3f52d65583"
/>
Now, each `strategy.authenticate` call is wrapped into `try` / `catch`,
if an error happens we use `logError` to correctly log that error by its
logging level.
Imports https://github.com/payloadcms/payload-admin-bar into the Payload
monorepo. This package will now be regularly maintained directly
alongside all Payload packages and now includes its own test suite.
A few changes minor have been made between v1.0.7 and latest:
1. The package name has changed from `payload-admin-bar` to
`@payloadcms/admin-bar`.
```diff
- import { PayloadAdminBar } from 'payload-admin-bar'
+ import { PayloadAdminBar } from '@payloadcms/admin-bar'
```
2. The `collection` prop has been renamed to `collectionSlug`
3. The `authCollection` prop has been renamed to `authCollectionSlug`
Here's a screenshot of the admin bar in use within the Website Template:
<img width="1057" alt="Screenshot 2025-03-05 at 1 20 04 PM"
src="https://github.com/user-attachments/assets/2597a8fd-da75-4b2f-8979-4fc8132999e8"
/>
---------
Co-authored-by: Kalon Robson <kalon.robson@outlook.com>
Fixes https://github.com/payloadcms/payload/issues/11473
Previously, when `disablePayloadAccessControl: true` was defined, client
uploads were working improperly. The reason is that
`addDataAndFileToRequest` expects `staticHandler` to be defined and we
don't add in case if `disablePayloadAccessControl: true`.
This PR makes it so otherwise and if we have `clientUploads`, it pushes
the "proxied" handler that responses only when the file was requested in
the context of client upload (from `addDataAndFileToRequest`)
### What?
Regression caused by https://github.com/payloadcms/payload/pull/11433
If a beforeChange hook was checking for a missing or undefined `value`
in order to change the value before inserting into the database, data
could be lost.
### Why?
In #11433 the logic for setting the fallback field value was moved above
the logic that cleared the value when access control returned false.
### How?
This change ensures that the fallback value is passed into the
beforeValidate function _and_ still available with the fallback value on
siblingData if access control returns false.
Fixes https://github.com/payloadcms/payload/issues/11543
Adds a new `admin.disableBlockName` property that allows you to disable
the blockName field entirely in the admin view. It defaults to false for
backwards compatibility.
<!--
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 removes two links from a time where there was two distinct
live-preview examples. It also adjusts links for CORS and CSRF to a more
appropriate location in the docs.
### Why?
Now there's only the App Router example, so direct users there instead.
### How?
Changes to `docs/live-preview/client.mdx`
This PR updates the field `condition` function property to include a new
`path` argument.
The `path` arg provides the schema path of the field, including array
indices where applicable.
#### Changes:
- Added `path: (number | string)[]` in the Condition type.
- Updated relevant condition checks to ensure correct parameter usage.
When rendering views within a drawer outside of the edit view, i.e. from
the list view, it updates the underlying step nav to the collection of
the drawer. This is true for both document drawers and list drawers.
This is because the logic controlling this behavior relies on the
current edit depth, which is only incremented within the edit view
itself. Instead of doing this, we can conditionally run the setter
functions based the presence of a drawer slug.
An alternative to this would be to subscribe to the `drawerDepth`
context but this would be less efficient, as this requires an
unnecessary hook and subsequent rendering cycle.
Cleans up various Payload-generated configs, namely:
- Renames config entry files from `preferencesCollection.ts`,
`lockedDocumentsCollection.ts`, and `jobsCollection.ts` to `config.ts`
- Standardizes collection slugs for `payload-preferences`,
`payload-locked-documents`, and `payload-jobs` and reuses everywhere
- Renames camel-cased `payloadPreferences` directory to kebab case, i.e.
`payload-preferences`
Client uploads were always enabled because a wrong variable was used,
when passing `enabled` to `initClientUploads`,
`gcsStorageOptions.enabled` instead of `gcsStorageOptions.clientUploads`
To enable client uploads with GCS you also additionally need to
configure CORS on Google Cloud, therefore this change breaks existing
logic
### What?
This PR adds ability to define indexes on several fields for collections
(compound indexes).
Example:
```ts
{
indexes: [{ unique: true, fields: ['title', 'group.name'] }]
}
```
### Why?
This can be used to either speed up querying/sorting by 2 or more fields
at the same time or to ensure uniqueness between several fields.
### How?
Implements this logic in database adapters. Additionally, adds a utility
`getFieldByPath`.
By default, Payload only attempts to locate the import map file in the following locations:
- `src/app/(payload)/{adminroute}/importMap.js`
- `app/(payload)/{adminroute}/importMap.js`
This is fine for most projects, but sometimes you may want to place the import map - or the Payload admin directory - somewhere else.
This PR adds a new `importMapFile` property that allows you to override this heuristic and specify your own import map path.
Adds new plugin-import-export initial version.
Allows for direct download and creation of downloadable collection data
stored to a json or csv uses the access control of the user creating the
request to make the file.
config options:
```ts
/**
* Collections to include the Import/Export controls in
* Defaults to all collections
*/
collections?: string[]
/**
* Enable to force the export to run synchronously
*/
disableJobsQueue?: boolean
/**
* This function takes the default export collection configured in the plugin and allows you to override it by modifying and returning it
* @param collection
* @returns collection
*/
overrideExportCollection?: (collection: CollectionOverride) => CollectionOverride
// payload.config.ts:
plugins: [
importExportPlugin({
collections: ['pages', 'users'],
overrideExportCollection: (collection) => {
collection.admin.group = 'System'
collection.upload.staticDir = path.resolve(dirname, 'uploads')
return collection
},
disableJobsQueue: true,
}),
],
```
---------
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Co-authored-by: Kendell Joseph <kendelljoseph@gmail.com>
If `experimental.fullySpecified` is set to `true` in the next config, the Payload admin panel fails to compile, throwing the following error:
```ts
Failed to compile.
../../node_modules/.pnpm/@payloadcms+next@3.25.0-canary.46647b4_@types+react@18.3.1_graphql@16.10.0_monaco-editor@0.40_w3ro7ziou6gzev7zbe3qqrwaqe/node_modules/@payloadcms/next/dist/views/Version/RenderFieldsToDiff/fields/Select/DiffViewer/index.js
Attempted import error: 'DiffMethod' is not exported from 'react-diff-viewer-continued' (imported as 'DiffMethod').
```
The issue stems from incorrect import statements in `react-diff-viewer-continued` 4.0.4. This was fixed in `react-diff-viewer-continued` 4.0.5.
This PR also enables `fullySpecified` in our test suites, to catch these issues going forward.
Adding usage of `ScribeMD/docker-cache` to cache the mongodb image.
We utilize the
[supercharge/mongodb-github-action](https://github.com/supercharge/mongodb-github-action)
for pulling and starting our mongo image. This would at times cause `You
have reached your unauthenticated pull rate limit` errors because of how
many jobs our CI spins up at one time.
Deep‐merging the collection config defaults during sanitization causes all collection fields to end up with different object references. This is not only slow, but can also lead to unpredictable behavior: mutations made before collection sanitization are reflected in the field config, while mutations made afterward, using the same object reference, are not reflected in the collection’s field config.
Specifically, the following happened:
1. A Block was defined in the module scope.
2. It was then added to both a collection’s blocks field and the config.blocks property.
3. Rich text sanitization promises for config.blocks were collected.
4. The collection config was sanitized.
5. The config.blocks sanitization promises were awaited.
6. Rich text fields were sanitized in config.blocks, but ended up not being sanitized in the collection config referencing the same block, because the object reference held by the promise callback no longer matched the collection config’s object reference. The collection config block did not create its own rich text sanitization promise, as `_sanitized: true` was set on the block during the earlier config.blocks sanitization, which skipped it.
Our config defaults pattern was brittle in general. It’s easy to misuse object spreading or to mutate the config defaults later when you intended only to mutate the payload or collection config. Our current approach was vulnerable to this because it retained some object references from the config defaults.
This PR introduces reliable merge functions that are faster and ensure no object references are shared with defaults that reside in the module scope.
### What?
Supersedes https://github.com/payloadcms/payload/pull/11490.
Refactors imports of `formatAdminURL` to import from `payload/shared`
instead of `@payloadcms/ui/shared`. The ui package now imports and
re-exports the function to prevent this from being a breaking change.
### Why?
This makes it easier for other packages/plugins to consume the
`formatAdminURL` function instead of needing to implement their own or
rely on the ui package for the utility.
When uploading file via client side upload we invalidate it then on the
server side with re-uploading. This works fine with most adapters since
they just replace the old file under the same key. UploadThing works
differently and generates a new key every time.
Example of the issue:
<img width="611" alt="image"
src="https://github.com/user-attachments/assets/9c01b52a-d159-4f32-9f66-3b5fbadab7b4"
/>
Now, we clear the old file before doing re-upload.
Previously, collections with similar names (e.g., `uploads` and
`uploads-poly`) both appeared active when viewing either collection.
This was due to `pathname.startsWith(href)`, which caused partial
matches.
This update refines the `isActive` logic to prevent partial matches.
Previously, AVIF images were not converted to other file types as
expected, despite `upload.formatOptions` specifying a different file
type.
The issue was due to `canResizeImage` not recognizing `'image/avif',`
causing `fileSupportsResize` to return `false` and preventing the image
from undergoing format conversion.
This fix updates `canResizeImage` to include `'image/avif'`, ensuring
that AVIF images are processed correctly and converted to a different
file type when specified in `formatOptions`.
Fixes#10694Fixes#9985
### What
Clarifies that `sharp` must be specified in payload config for image
resizing & cropping to work. Also adds link to the configuration page
for further information.
### Why
It is not immediately clear from this single documentation page alone.
While it says that the feature relies on sharp, it does not say that it
must be added to config. Most people won't probably run into this since
they're probably going to use `create-payload-app`, which configures
sharp by default. But those who use custom config (like me) may be left
wondering why this feature does not work.
See [Crop images and preview sizes not
working](https://payloadcms.com/community-help/discord/crop-images-and-preview-sizes-not-working)
in community help.
### What?
There were a couple issues with the implementation within the example
when using postgres.
- `ensureUniqueUsername` tenant was being extracted incorrectly, should
not constrain query unless it was present
- `ensureUniqueSlug` was querying by NaN when tenant was not present on
data or originalDoc
- `users` read access was not correctly extracting the tenant id in the
correct type depending on DB
Fixes https://github.com/payloadcms/payload/issues/11484
The `payload-admin-bar` now supports React 19 as a result of
https://github.com/payloadcms/payload-admin-bar/pull/13. This will
suppress the React 19 warnings on install within the website templates
and various examples that rely on this package.
This update improves the `Environment Info` section in the issue
template by asking users to provide exact version numbers instead of
"latest."
This ensures that bug reports remain accurate and useful over time.