drizzle: make radio and select column names to snake_case (#8439) (0128eed)
Fixes https://github.com/payloadcms/payload/issues/8402 and https://github.com/payloadcms/payload/issues/8027
Before DB column names were camelCase:
After this change, they are snake_case:
If you had any select (not hasMany: true
) or radio fields with the
name in camelCase, for example:
{
name: 'selectReadOnly',
type: 'select',
admin: {
readOnly: true,
},
options: [
{
label: 'Value One',
value: 'one',
},
{
label: 'Value Two',
value: 'two',
},
],
},
This previously was mapped to the db column name "selectReadOnly"
. Now
it's select_read_only
.
Generate a new migration to rename your columns.
pnpm payload migrate:create
Then select "rename column" for targeted columns and Drizzle will handle the migration.
update next@15.0.0-canary.173, react@19.0.0-rc-3edc000d-20240926 (#8489) (fa59d4c)
Updates the minimal supported versions of next.js to
15.0.0-canary.173
and react to 19.0.0-rc-3edc000d-20240926
.
Adds neccessary awaits according to this breaking change https://github.com/vercel/next.js/pull/68812
The params
and searchParams
types in
app/(payload)/admin/[[...segments]]/page.tsx
and
app/(payload)/admin/[[...segments]]/not-found.tsx
must be changed to
promises:
- type Args = {
- params: {
- segments: string[]
- }
- searchParams: {
- [key: string]: string | string[]
- }
- }
+ type Args = {
+ params: Promise<{
+ segments: string[]
+ }>
+ searchParams: Promise<{
+ [key: string]: string | string[]
+ }>
+ }
number
field values with the exists
operator filter (#8416) (8110cb9)richtext-lexical: upgrade lexical from 0.17.0 to 0.18.0, make tables more reliable (#8444) (8b44676)
This upgrades lexical from 0.17.0 to 0.18.0. If you have any lexical packages installed in your project, please update them accordingly. Additionally, if you depend on the lexical APIs, please consult their changelog, as lexical may introduce breaking changes: https://github.com/facebook/lexical/releases/tag/v0.18.0
live-preview
view (#8343) (57f93c9)improve afterError hook to accept array of functions, change to object args (#8389) (28ea0c5)
Changes the afterError
hook structure, adds tests / more docs.
Ensures that the req.responseHeaders
property is respected in the
error handler.
afterError
now accepts an array of functions instead of a single
function:
- afterError: () => {...}
+ afterError: [() => {...}]
The args are changed to accept an object with the following properties:
| Argument | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| error
| The error that occurred. |
| context
| Custom context passed between Hooks. More details. |
| graphqlResult
| The GraphQL result object, available if the hook is executed within a GraphQL context. |
| req
| The Request object containing the currently authenticated user
|
| collection
| The Collection in which this Hook is running against. This will be undefined
if the hook is executed from a non-collection endpoint or GraphQL. |
| result
| The formatted error result object, available if the hook is executed from a REST context. |
overrideLock
flag to update
& delete
operations (#8294) (9c3f863)richtext-lexical: dropdown menu disabled status (#8177) (7d2022f)
ToolbarDropdown no longer receives
groupKey={group.key}
and items={group.items}
as props, but instead
group={group}
plugin-form-builder: emails array field has read access by authenticated users only by default now (#8338) (0789f4d)
upgrade next, react and react-dom, move react/next dependency checker from payload to next package (#8323) (1afcaa3)
db-mongodb
changes relationships to be stored as ObjectIDs instead of strings (for now querying works using both types internally to the DB so no data migration should be necessary unless you're querying directly, see breaking changes for detailsexport const Categories: CollectionConfig = {
slug: 'categories',
fields: [
{
name: 'relatedPosts',
type: 'join',
collection: 'posts',
on: 'category',
}
]
}
All mongodb relationship and upload values will be stored as MongoDB ObjectIDs instead of strings going forward. If you have existing data and you are querying data directly, outside of Payload's APIs, you get different results. For example, a contains
query will no longer works given a partial ID of a relationship since the ObjectID requires the whole identifier to work.
drizzle: localized fields uniqueness per locale (#8230) (b7db53c)
Previously, this worked with MongoDB but failed with Postgres / SQLite
when the slug
field has both localized: true
and unique: true
.
await payload.create({
collection: "posts",
locale: "en",
data: {
slug: "my-post"
}
})
await payload.create({
collection: "posts",
locale: "de",
data: {
slug: "my-post"
}
})
Now, we build unique constraints and indexes in combination with the _locale column. This should also improve query performance for fields with both index: true and localized: true.
This change updates the database schema and requires a migration (if you have any localized fields). To apply it, run the following commands:
pnpm payload migrate:create locale_unique_indexes
pnpm payload migrate
Note that if you use db.push: true
which is a default, you don't have
to run pnpm payload migrate
in the development mode, only in the
production, as Payload automatically pushes the schema to your DB with
it.
isEnabled
computations on toolbar items (#8176) (334f940)This PR changes the type of selected
returned from the useSelection
hook from the SelectionProvider
from an object to a Map.
This fixes a bug where in some situations we lose the type of the ID which can break data entry when using postgres, due to keys being cast to strings inside of objects which doesn't happen when using a Map.
This PR also fixes a CSS bug with the checkbox when it should be partially selected.
// before
selected: Record<number | string, boolean>
// after
selected: Map<number | string, boolean>
This means you now need to read the data differently than before.
// before
Object.entries(selected).forEach(([key, value]) => {
// do something
})
// after
for (const [key, value] of selected) {
// do something
}
properly names BlocksField
and related types (#8174) (465e47a)
The BlockField
type is not representative of the underlying "blocks"
field type, which is plural, i.e. BlocksField
. This is a semantic
change that will better align the type with the field.
Types related to the blocks
field have change names. If you were using
the BlockField
or related types in your own applications, simply
change the import name to be plural and instead of singular.
Old (singular):
import type {
BlockField,
BlockFieldClient,
BlockFieldValidation,
BlockFieldDescriptionClientComponent,
BlockFieldDescriptionServerComponent,
BlockFieldErrorClientComponent,
BlocksFieldErrorServerComponent,
BlockFieldLabelClientComponent,
BlockFieldLabelServerComponent,
} from 'payload'
New (plural):
import type {
BlocksField,
BlocksFieldClient,
BlocksFieldValidation,
BlocksFieldDescriptionClientComponent,
BlocksFieldDescriptionServerComponent,
BlocksFieldErrorClientComponent,
BlocksFieldErrorServerComponent,
BlocksFieldLabelClientComponent,
BlocksFieldLabelServerComponent,
} from 'payload'
explicitly types field components (#8136) (8e1a5c8)
We are no longer exporting TextFieldProps
etc. for each field type.
Instead, we now export props for each client/server environment
explicitly. If you were previously importing one of these types into
your custom component, simply change the import name to reflect your
environment.
Old:
import type { TextFieldProps } from 'payload'
New:
import type { TextFieldClientProps, TextFieldServerProps } from 'payload'
richtext-lexical: greatly simplify lexical loading and improve performance (#8041) (b6a8d1c)
We noticed that we can bring functions down to the client directly without having to wrap them in a component first. This greatly simplifies the loading of all lexical client components
BREAKING:
createClientComponent
is no longer exported as it's not needed
anymoreClientComponentProps
type has been renamed to
BaseClientFeatureProps
.sanitizeClientEditorConfig
has changedauto-removes localized property from localized fields within other localized fields (#7933) (538b7ee)
Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized.
Up until this point, Payload would allow you to nest a localized field within another localized field, and this might have worked in MongoDB but it will throw errors in Postgres.
Now, Payload will automatically remove the localized: true
property
from sub-fields within sanitizeFields
if a parent field is localized.
This could potentially be a breaking change if you have a configuration with MongoDB that nests localized fields within localized fields.
You probably only need to migrate if you are using MongoDB, as there, you may not have noticed any problems. But in Postgres or SQLite, this would have caused issues so it's unlikely that you've made it too far without experiencing issues due to a nested localized fields config.
In the event you would like to keep existing data in this fashion, we
have added a compatibility.allowLocalizedWithinLocalized
flag to the
Payload config, which you can set to true
, and Payload will then
disable this new sanitization step.
Set this compatibility flag to true
only if you have an existing
Payload MongoDB database from pre-3.0, and you have nested localized
fields that you would like to maintain without migrating.
handles custom collection description components (#7789) (cb9b80a)
If you were previously defining a custom description component on a collection, simply move it into the correct position.
Old:
{
admin: {
components: {
edit: {
Description: ''
}
}
}
}
New:
{
admin: {
components: {
Description: ''
}
}
}
false
as PayloadComponent which signals that the component should not be rendered (#7682) (49a2d70)leave-without-saving
modal after navigating from Leave anyway
button (#7661) (806c22e)Follow along with the migration guide below for full details on each of the changes. Please reference this document as you migrate your own apps to this release.
So what's included in this release? By far the biggest change comes from this PR (migration guide below).
This PR made significant optimizations to the underlying codebase
For a complete list of all other bug fixes and features included in this release, scroll to the bottom of this document.
[!IMPORTANT] The payload config cannot import client files anymore. If you do that, you will get errors when running bin scripts, e.g. "Unknown file extension ".css". This means that you are still importing client modules somewhere in your config - could be components you're still importing without component paths, or older plugins that still import client modules into your config. Follow this migration guide to find out how you can migrate your config.
To migrate from beta.78
to beta.79
, you'll need to make a few changes to your Payload Config. Although this release is substantial, migration is relatively straightforward, with most of the changes only effecting the use of Custom Components.
The following changes are required of all projects:
Add a blank "import map" to your project. Create a new file called importMap.js
and add it to /app/(payload)/admin/importMap.js
in your app with the following content:
export const importMap = {}
Payload will dynamically inject this file with imports at compile time. If you are using a custom admin route, change the path accordingly.
It is recommended to run the payload generate:importMap
command after you're done with the migration (make sure you follow all migration steps below first). Additionally, if the automatic import map generation fails, try running payload generate:importMap
.
Thread the "import map" file through your Root Layout, Root Page, and Not Found Pages:
The Root Layout at /app/(payload)/layout.tsx
:
// ...
import { importMap } from "./admin/importMap.js"
//...
const Layout = ({ children }: Args) => (
<RootLayout config={configPromise} importMap={importMap}>
{children}
</RootLayout>
)
export default Layout
The Root Page at /app/(payload)/admin/[[...segments]]/page.tsx
:
// ...
import { importMap } from "../importMap.js"
//...
const Page = ({ params, searchParams }: Args) =>
RootPage({ config, importMap, params, searchParams })
export default Page
The Not Found Page at /app/(payload)/admin/[[...segments]]/not-found.tsx
:
// ...
import { importMap } from "../importMap.js"
//...
const NotFound = ({ params, searchParams }: Args) =>
NotFoundPage({ config, importMap, params, searchParams })
export default NotFound
If you are importing the Payload Config in a Node.js environment, there is no longer a need for a special loader to process it. Instead, you can simply import the Payload Config directly. To migrate, remove the importConfig
and importWithoutClientFiles
functions from your Node.js code:
Old:
import { importConfig, importWithoutClientFiles } from "payload"
import { join } from "path"
const config = importConfig(join(__dirname, "payload.config.js"))
const configWithoutClientFiles = importWithoutClientFiles(
join(__dirname, "payload.config.js")
)
New:
import config from "./payload.config.js"
If you are not using Custom Components in your applications, then you are done! If you are using Custom Components, please continue reading.
If you are using Custom Components in your application, please follow these next steps. Not all of these changes will apply to every project. So first, review this list of changes, and if a change applies to yours, scroll down to that individual section for full details:
useConfig
hook usage (if applicable)useComponentMap
hook usage (if applicable)useFieldProps
hook usage (if applicable)Here's a full breakdown of each step:
If you are using Custom Components in your Payload Config, the pattern for defining them has changed. You'll need to update your Custom Component definitions to use "component paths" instead of direct imports. To migrate, pass the path to the component's file:
Old:
import { MyCustomComponent } from './MyCustomComponent.js'
// ...
{
// ...
admin: {
components: {
Label: MyCustomComponent
},
},
}
New:
{
// ...
admin: {
components: {
// NOTE: this path is relative to your `baseDir`, NOT the importing file
// The `#` character is used to specify the export name, if necessary
Label: '/collections/Posts/MyServerComponent.js#MyExportName',
},
},
}
Regarding the extension in the component path: you'd write it as if you were to write an import statement yourself. Few examples, assuming the imports are done from the base directory:
If you'd previously write import { sth } from './file.js'
the component path would be '/file.js#sth'
if you'd previously write import { sth } from './file'
the component path would be '/file#sth'
if you'd previously write import file from './file.tsx'
the component path would be '/file.tsx'
or '/file.tsx#default'
Alternatively, you can also define the custom component as a "PayloadComponent" in the Payload Config. This allows you to pass in client-side vs server-side props explicitly, which get automatically sanitized from the client. Previously, you'd have to use an HOC, or a utility such as withMergedProps
in order to do that.
{
// ...
admin: {
components: {
Label: {
path: '/collections/Posts/MyServerComponent.js#MyExportName',
// exportName: 'MyComponent2' // optional if you wish to omit the export name from the path
clientProps: {
someClientProp: 'someValue'
},
serverProps: {
someServerProp: () => 'someValue'
}
}
},
},
}
More info regarding how the new component path imports work can be found in our custom component imports docs
useConfig
hook changesIf you are using the useConfig
React hook anywhere in your Custom Components, its return value has changed in shape. The config
is now a property within the context object, rather than the return value itself. To migrate, simply destructure the config
property from the return value of useConfig
:
Old:
import { useConfig } from "@payloadcms/ui"
// ...
const config = useConfig()
New:
import { useConfig } from "@payloadcms/ui"
// ...
const { config } = useConfig()
useComponentMap
hook was deprecatedIf you were using the useComponentMap
React hook anywhere in your Custom Components, it no longer exists. Instead, it has been merged into useConfig
. To migrate, swap it out for the new hook:
Old:
import { useComponentMap } from "@payloadcms/ui"
// ...
const { componentMap, getComponentMap } = useComponentMap()
const collectionComponentMap = getComponentMap({ collectionSlug: "pages" })
New:
import { useConfig } from "@payloadcms/ui"
// ...
const { config, getEntityConfig } = useConfig()
const collectionClientConfig = getEntityConfig({ collectionSlug: "pages" })
If you were using Custom Fields anywhere in your code, their props have changed in shape. Previously, field props were spread in at the top-level of the component. Now, they are nested under the field
key, which is a client-safe version of that field's config. To migrate, simply change your prop references to be nested under field
:
Old:
import type { TextFieldProps } from "payload"
const MyCustomField: : React.FC<TextFieldProps> = ({ name }) => {
return <div>{name}</div>
}
New:
import type { TextFieldProps } from "payload"
const MyCustomField: : React.FC<TextFieldProps> = ({ field: { name } }) => {
return <div>{name}</div>
}
This example demonstrates a simple text field, but this same thing applies to all field types. The type naming convention is consistent across all field types, so you can easily find the type definitions for your respective field type.
useFieldProps
is not necessary anymoreIf you were using the useFieldProps
hook in your Custom Client Components, this is no longer needed. Now, client components are automatically detected and rendered client-side which direct props. To migrate your Custom Client Components, simply remove the useFieldProps
hook from your component and read directly from props:
Old:
"use client"
import type { TextFieldProps } from "payload"
import { useFieldProps } from "@payloadcms/ui"
const MyCustomClientComponent: React.FC<TextFieldProps> = () => {
const { path } = useFieldProps()
return <div>{path}</div>
}
New:
"use client"
import type { TextFieldProps } from "payload"
const MyCustomClientComponent: React.FC<TextFieldProps> = ({
field: { _path },
}) => {
return <div>{_path}</div>
}
The shape of Custom Views and Custom Tabs has changed. Previously, you could define Custom Views and Tabs top-level in the config object OR as a configuration object. Now, you can only define them as a configuration object. For this reason, the keys have changed in casing to reflect the new shape (CapitalCase
denotes a React component, camelCase
denotes a regular object). To migrate, simply change the casing of of these keys within your Payload Config:
Old:
{
// ...
admin: {
components: {
views: {
Edit: {
Default: {
Tab: MyCustomTab
},
API: MyCustomAPIView
}
}
}
}
}
New:
{
// ...
admin: {
components: {
views: {
edit: {
default: {
tab: {
// ...
}
},
api: {
// ...
}
}
}
}
}
}
This example demonstrates only a subset of the Custom Views that are available in the Payload Config. The same change applies to all Custom Views and Tabs, such as admin.views.account
at the root-level, or admin.views.list
on Collection Configs.
This release also includes other miscellaneous features and fixes, including:
This lowers the module count by 31 modules
BREAKING: Migration-related lexical modules are now exported from
@payloadcms/richtext-lexical/migrate
instead of
@payloadcms/richtext-lexical
relationships
with drafts
enabled (#7570) (8d12037)ArrayCell
when length is 1 (#7586) (f88cef5)adjusts auth hydration from server (#7545) (e905675)
Fixes https://github.com/payloadcms/payload/issues/6823
Allows the server to initialize the AuthProvider via props. Renames
HydrateClientUser
to HydrateAuthProvider
. It now only hydrates the
permissions as the user can be set from props. Permissions can be
initialized from props, but still need to be hydrated for some pages as
access control can be specific to docs/lists etc.
HydrateClientUser
to HydrateAuthProvider
⚠️ bump next canary to 104 and update withPayload for new config (#7541) (8d1fc6e)
We are now bumping up the Next canary version to 15.0.0-canary.104
and
react
and react-dom
to ^19.0.0-rc-06d0b89e-20240801
.
Your new dependencies should look like this:
"next": "15.0.0-canary.104",
"react": "^19.0.0-rc-06d0b89e-20240801",
"react-dom": "^19.0.0-rc-06d0b89e-20240801",
richtext-lexical: html converters not respecting overrideAccess property when populating values, in local API (c15d679)
hasMany
text going outside of input boundaries (#7455) (3f5403a)payload.db.defaultIDType
(#7416) (62666a9)updated admin UI (#7424) (68553ff)
Merriweather
font, you will need to manually
configure next/font
in your own project.richtext-lexical: upgrade lexical from 0.16.1 to 0.17.0 (e65b647)
basePath
into logout component link (#7451) (3d89508)abort()
call signal error (#7390) (5655266)ui: passes field props to custom components (#7360) (97837f0)
Currently, there is no way to read field props from within a custom
field component, i.e. admin.components.Description
. For example, if
you set maxLength: 100
on your field, your custom description
component cannot read it from props.maxLength
or any other methods.
Because these components are rendered on the server, there is also no
way of using admin.component.Field
to inject custom props yourself,
either. To support this, we can simply pass the base component props
into these components on the server, as expected. This has also led to
custom field component props becoming more strictly typed within the
config.
This change is considered breaking only because the types have changed. This only affects you if you were previously importing the following types into your own custom components. To migrate, simply change the import paths for that type.
Old:
import type {
ArrayFieldProps,
ReducedBlock,
BlocksFieldProps,
CheckboxFieldProps,
CodeFieldProps,
CollapsibleFieldProps,
DateFieldProps,
EmailFieldProps,
GroupFieldProps,
HiddenFieldProps,
JSONFieldProps,
NumberFieldProps,
PointFieldProps,
RadioFieldProps,
RelationshipFieldProps,
RichTextComponentProps,
RowFieldProps,
SelectFieldProps,
TabsFieldProps,
TextFieldProps,
TextareaFieldProps,
UploadFieldProps,
ErrorProps,
FormFieldBase,
FieldComponentProps,
FieldMap,
MappedField,
MappedTab,
ReducedBlock,
} from '@payloadcms/ui'
New:
import type {
FormFieldBase,
// etc.
} from 'payload'
Custom field components are now much more strongly typed. To make this
happen, an explicit type for every custom component has been generated
for every field type. The convention is to append
DescriptionComponent
, LabelComponent
, and ErrorComponent
onto the
end of the field name, i.e. TextFieldDescriptionComponent
. Here's an
example:
import type { TextFieldDescriptionComponent } from 'payload'
import React from 'react'
export const CustomDescription: TextFieldDescriptionComponent = (props) => {
return (
<div id="custom-field-description">{`The max length of this field is: ${props?.maxLength}`}</div>
)
}
Here's the full list of all new types:
Label Components:
import type {
ArrayFieldLabelComponent,
BlocksFieldLabelComponent,
CheckboxFieldLabelComponent,
CodeFieldLabelComponent,
CollapsibleFieldLabelComponent,
DateFieldLabelComponent,
EmailFieldLabelComponent,
GroupFieldLabelComponent,
HiddenFieldLabelComponent,
JSONFieldLabelComponent,
NumberFieldLabelComponent,
PointFieldLabelComponent,
RadioFieldLabelComponent,
RelationshipFieldLabelComponent,
RichTextFieldLabelComponent,
RowFieldLabelComponent,
SelectFieldLabelComponent,
TabsFieldLabelComponent,
TextFieldLabelComponent,
TextareaFieldLabelComponent,
UploadFieldLabelComponent
} from 'payload'
Error Components:
import type {
ArrayFieldErrorComponent,
BlocksFieldErrorComponent,
CheckboxFieldErrorComponent,
CodeFieldErrorComponent,
CollapsibleFieldErrorComponent,
DateFieldErrorComponent,
EmailFieldErrorComponent,
GroupFieldErrorComponent,
HiddenFieldErrorComponent,
JSONFieldErrorComponent,
NumberFieldErrorComponent,
PointFieldErrorComponent,
RadioFieldErrorComponent,
RelationshipFieldErrorComponent,
RichTextFieldErrorComponent,
RowFieldErrorComponent,
SelectFieldErrorComponent,
TabsFieldErrorComponent,
TextFieldErrorComponent,
TextareaFieldErrorComponent,
UploadFieldErrorComponent
} from 'payload'
Description Components:
import type {
ArrayFieldDescriptionComponent,
BlocksFieldDescriptionComponent,
CheckboxFieldDescriptionComponent,
CodeFieldDescriptionComponent,
CollapsibleFieldDescriptionComponent,
DateFieldDescriptionComponent,
EmailFieldDescriptionComponent,
GroupFieldDescriptionComponent,
HiddenFieldDescriptionComponent,
JSONFieldDescriptionComponent,
NumberFieldDescriptionComponent,
PointFieldDescriptionComponent,
RadioFieldDescriptionComponent,
RelationshipFieldDescriptionComponent,
RichTextFieldDescriptionComponent,
RowFieldDescriptionComponent,
SelectFieldDescriptionComponent,
TabsFieldDescriptionComponent,
TextFieldDescriptionComponent,
TextareaFieldDescriptionComponent,
UploadFieldDescriptionComponent
} from 'payload'
This PR also:
FieldBase['label']
type with a new LabelStatic
type. This makes type usage much more consistent across components.<Omit>
, etc.ui: update the names of internal components so that they respect eslint rules (#7362) (e734d51)
So _Upload
becomes UploadComponent
which doesnt break the naming
convention of react components and we no longer export these internal
components
700% faster deepCopyObject, refactor deep merging and deep copying, type improvements (#7272) (c45fbb9)
deepMerge
exported from payload now handles more complex data and
is slower. The old, simple deepMerge is now exported as deepMergeSimple
combineMerge
is no longer exported. You can use
deepMergeWithCombinedArrays
insteaddeepCopyObject
and isPlainObject
may
be different and more reliable, as the underlying algorithm has changednew server-only, faster and immediate autoLogin (#7224) (a7b0f8b)
autoLogin
without prefillOnly
set now also affects graphQL/Rest
operations. Only the user specified in autoLogin
will be returned.
Within the graphQL/Rest/Local API, this should still allow you to
authenticate with a different user, as the autoLogin user is only used
if no token is set.Duplicate of #7146 but for beta. If you were using the AfterMeHook
, AfterLogoutHook
, or AfterRefreshHook
, these types have been renamed and are now prefixed with Collection
to match all other hook naming conventions, just as the documentation already indicates.
Old:
import type { AfterMeHook, AfterLogoutHook, AfterRefreshHook } from 'payload'
New:
import type { CollectionAfterMeHook, CollectionAfterLogoutHook, CollectionAfterRefreshHook } from 'payload'
Fixes publishing issue
BREAKING:
- Upgrades minimum supported @types/react version from npm:types-react@19.0.0-beta.2 to npm:types-react@19.0.0-rc.0
- Upgrades minimum supported @types/react-dom version from npm:types-react-dom@19.0.0-beta.2 to npm:types-react-dom@19.0.0-rc.0
- Upgrades minimum supported react and react-dom version from 19.0.0-rc-f994737d14-20240522 to 19.0.0-rc-6230622a1a-20240610
plugin-search: make search collection fields override into a function that provides defaultFields inline with other plugins (#7095) (2bc8666)
searchPlugin's searchOverrides for the collection now takes in a fields function instead of array similar to other plugins and patterns we use to allow you to map over existing fields as well if needed.
// before
searchPlugin({
searchOverrides: {
slug: 'search-results',
fields: [
{
name: 'excerpt',
type: 'textarea',
admin: {
position: 'sidebar',
},
},
]
},
}),
// current
searchPlugin({
searchOverrides: {
slug: 'search-results',
fields: ({ defaultFields }) =[
...defaultFields,
{
name: 'excerpt',
type: 'textarea',
admin: {
position: 'sidebar',
},
},
]
},
}),
exports getSiblingData, getDataByPath, and reduceFieldsToValues from payload (#7070) (0a2ecf8)
Exports getSiblingData
, getDataByPath
, reduceFieldsToValues
, and
unflatten
from payload
. These utilities were previously accessible
using direct import paths from @payloadcms/ui
—but this is no longer
advised since moving to a pre-bundled UI library pattern. Instead of
simply exporting these from the @payloadcms/ui
package, these exports
have been moved to Payload itself to provision for use outside of React
environments.
This is considered a breaking change. If you were previously importing any of these utilities, the imports paths have changed as follows:
Old:
import { getSiblingData, getDataByPath, reduceFieldsToValues } from '@payloadcms/ui/forms/Form'
import { unflatten } from '@payloadcms/ui/utilities'
New:
import { getSiblingData, getDataByPath, reduceFieldsToValues, unflatten } from 'payload/shared'
The is-buffer
dependency was also removed in this PR.
updated admin panel color palette (#7011) (c2022f6)
Color values have changed and will have different contrasts. If you use any of Payload's colors in your apps, you may need to adjust your use of them to maintain proper styling/accessibility.
Colors palettes changed:
--theme-success-*
--theme-error-*
--theme-warning-*
--color-success-*
--color-error-*
--color-warning-*
--color-blue-*
Updates the color palette used throughout Payload to be more consistent
between dark and light values. Contrast values are now more in line with
the theme-elevation
contrasts. Some adjustments to the Toast
components as well to match light/dark mode better.
BREAKING: The minimum required Next.js version has been bumped from
15.0.0-rc.0
to15.0.0-canary.53
. This is because the way client components are represented changed somewhere between those versions, and it is not feasible to support both versions in our RSC detection logic.
BREAKING: Lexical may introduce undocumented breaking changes, if you use the lexical API directly. Please consult their changelog: https://github.com/facebook/lexical/releases/tag/v0.16.1
Removes PayloadRequestWithData in favour of just PayloadRequest with optional types for
data
andlocale
addDataAndFileToRequest
andaddLocalesToRequestFromData
now takes in a single argument instead of an object// before await addDataAndFileToRequest({ request: req }) addLocalesToRequestFromData({ request: req }) // current await addDataAndFileToRequest(req) addLocalesToRequestFromData(req)
ValidationError
now requires the global
or collection
slug, as well as an errors
property. The actual errors are no longer at the top-level.
Changed the data to correctly match type generic being sent to the generate functions. So now you can type your generateTitle etc. functions like this
// before
const generateTitle: GenerateTitle = async <Page>({ doc, locale }) => {
return `Website.com — ${doc?.title?.value}`
}
// curent
import type { GenerateDescription, GenerateTitle, GenerateURL } from '@payloadcms/plugin-seo/types'
import type { Page } from './payload-types'
const generateTitle: GenerateTitle<Page> = async ({ doc, locale }) => {
return `Website.com — ${doc?.title}`
}
const generateDescription: GenerateDescription<Page> = async ({ doc, locale }) => {
return doc?.excerpt || 'generated description'
}
const generateURL: GenerateURL<Page> = async ({ doc, locale }) => {
return `https://yoursite.com/${locale ? locale + '/' : ''}${doc?.slug || ''}`
}
Breaking change because it was previously a FormState value.
Some authentication strategies may need to set headers for responses, such as updating cookies via a refresh token, and similar. This PR extends Payload's auth strategy capabilities with a manner of accomplishing this.
This is a breaking change if you have custom authentication strategies in Payload's 3.0 beta. But it's a simple one to update.
Instead of your custom auth strategy returning the user
, now you must
return an object with a user
property.
This is because you can now also optionally return responseHeaders
,
which will be returned by Payload API responses if you define them in
your auth strategies. This can be helpful for cases where you need to
set cookies and similar, directly within your auth strategies.
Before:
return user
After:
return { user }
Properties within the Custom Collection Components config were not
properly cased. In the Payload Config, there are places where we expose
an array of Custom Components to render. These properties should be
cased in camelCase
to indicate that its type is not a component,
but rather, it's an array of components. This is how all other
arrays are already cased throughout the config, therefore these
components break exiting convention. The CapitalCase
convention is
reserved for components themselves, however, fixing this introduces a
breaking change. Here's how to migrate:
Old:
{
// ...
admin: {
components: {
AfterList: [],
AfterListTable: [],
BeforeList: [],
BeforeListTable: [],
}
}
}
New:
{
// ...
admin: {
components: {
afterList: [],
afterListTable: [],
beforeList: [],
beforeListTable: [],
}
}
}
The docs were also out of date for the Root-level Custom Components. These components are documented in CaptalCase but are in fact cased correctly in Payload. This PR fixes that.
various type improvements (#6385) (ccbaee4)
relationTo
props on filterOptions, relationship
fields and upload fieldsStandardizes all named field exports. This improves semantics when using these components by appending Field
onto the end of their names. Some components were already doing this, i.e. ArrayField
and BlocksField
. Now, all field components share this same convention. And since bundled components were already aliasing most exports in this way, this change will largely go unnoticed because most apps were already importing the correctly named components. What is ultimately means is that there was a mismatch between the unbundled vs bundled exports. This PR resolves that conflict. But this also introduces a potentially breaking change for your app. If your app is using components that import from the unbundled @payloadcms/ui
package, those import paths likely changed:
Old:
import { Text } from '@payloadcms/ui/fields/Text'
New:
import { TextField } from '@payloadcms/ui/fields/Text'
If you were importing direcetly from the bundled version, you're imports likely have not changed. For example:
This still works (the import path is top-level, pointing to the bundled code):
import { TextField } from '@payloadcms/ui'
A bunch of exports have been moved around. There are now two of them: @payloadcms/richtext-lexical
and @payloadcms/richtext-lexical/client
. The root export is server-only. If any imports don't resolve anymore after this version, simply change the import to one of those, depending on if you are on the server or the client
richtext-lexical: simplify creation of features (#6885) (d66b348)
ClientComponent
has been renamed to ClientFeature
serverFeatureProps
has been renamed to
sanitizedServerFeatureProps
clientFeatureProps
has been renamed to
sanitizedClientFeatureProps
BREAKING: All
@payloadcms/ui/client
exports have been renamed to@payloadcms/ui
. A simple find & replace across your entire project will be enough to migrate. This change greatly improves import auto-completions in IDEs which lack proper support for package.json exports, like Webstorm.
basePath
not handled for logout
route (#6817) (47ee40a)Exports from the payload
package have been significantly cleaned up. Now, just about everything is able to be imported from payload
directly, rather than an assortment of subpath exports. This means that things like import { buildConfig } from 'payload/config'
are now just imported via import { buildConfig } from 'payload'
. The mental model is significantly simpler for developers, but you might need to update some of your imports.
Payload now exposes only three exports:
payload
- all types and server-only Payload codepayload/shared
- utilities that can be used in either the browser or in Node environmentspayload/node
- heavy utilities that should only be imported in Node scripts and never be imported into bundled code like Next.jsWith this release, we've dramatically sped up the compile time for Payload by pre-bundling our entire UI package for use inside of the Payload admin itself. There are new exports that should be used within Payload custom components:
@payloadcms/ui/client
- all client components@payloadcms/ui/server
- all server componentsFor all of your custom Payload admin UI components, you should be importing from one of these two pre-compiled barrel files rather than importing from the more deeply nested exports directly. That will keep compile times nice and speedy, and will also make sure that the bundled JS for your admin UI is kept small.
For example, whereas before, if you imported the Payload Button
, you would have imported it like this:
import { Button } from '@payloadcms/ui/elements/Button'
Now, you would import it like this:
import { Button } from '@payloadcms/ui/client'
This is a significant DX / performance optimization that we're pretty pumped about.
However, if you are importing or re-using Payload UI components outside of the Payload admin UI, for example in your own frontend apps, you can import from the individual component exports which will make sure that the bundled JS is kept to a minimum in your frontend apps. So in your own frontend, you can continue to import directly to the components that you want to consume rather than importing from the pre-compiled barrel files.
Individual component exports will now come with their corresponding CSS and everything will work perfectly as-expected.
'@payloadcms/ui/templates/Default'
and '@payloadcms/ui/templates/Minimal
' are now exported from '@payloadcms/next/templates'
LogOut
icon. Old: import { LogOut } from '@payloadcms/ui/icons/LogOut'
New: import { LogOutIcon } from '@payloadcms/ui/icons/LogOut'
In effort to make local dev as fast as possible, we need to import as few files as possible so that the compiler has less to process. One way we've achieved this in the Admin Panel was to remove all .scss imports from all components in the @payloadcms/ui
module using a build process. This stripped all import './index.scss'
statements out of each component before injecting them into dist
. Instead, it bundles all of the CSS into a single main.css
file, and we import that at the root of the app.
While this concept is still the right solution to the problem, this particular approach is not viable when using these components outside the Admin Panel, where not only does this root stylesheet not exist, but where it would also bloat your app with unused styles. Instead, we need to keep these .scss imports in place so they are imported directly alongside your components, as expected. Then, we need create a new build step that separately compiles the components without their stylesheets—this way your app can consume either as needed from the new client
and server
barrel files within @payloadcms/ui
, i.e. from within @payloadcms/next
and all other admin-specific packages and plugins.
This way, all other applications will simply import using the direct file paths, just as they did before. Except now they come with stylesheets.
And we've gotten a pretty awesome initial compilation performance boost.
- Makes Gravatar the default
BREAKING
- Our internal field hook methods now have new required
schemaPath
and pathprops
. This affects the following functions, if you are using those:afterChangeTraverseFields
,afterReadTraverseFields
,beforeChangeTraverseFields
,beforeValidateTraverseFields
,afterReadPromise
- The afterChange field hook's
value
is now the value AFTER the previous hooks were run. Previously, this was the original value, which I believe is a bug- Only relevant if you have built your own richText adapter: the richText adapter
populationPromises
property has been renamed tographQLPopulationPromises
and is now only run for graphQL. Previously, it was run for graphQL AND the rest API. To migrate, usehooks.afterRead
to run population for the rest API- Only relevant if you have built your own lexical features: The
populationPromises
server feature property has been renamed tographQLPopulationPromises
and is now only run for graphQL. Previously, it was run for graphQL AND the rest API. To migrate, usehooks.afterRead
to run population for the rest API- Serialized lexical link and upload nodes now have a new
id
property. While not breaking, localization / hooks will not work for their fields until you have migrated to that. Re-saving the old document on the new version will automatically add theid
property for you. You will also get a bunch of console logs for every lexical node which is not migrated
metadata.pages
for height if animated (#6728) (10c6ffa)fileHasAdjustments
files or fileIsAnimated
files (#6708) (6512d5c)We now export toast from
sonner
instead ofreact-toastify
. If you send out toasts from your own projects, make sure to use ourtoast
export, or installsonner
. React-toastify toasts will no longer work anymore. The Toast APIs are mostly similar, but there are some differences if you provide options to your toastCSS styles have been changed from Toastify
/* before */ .Toastify /* current */ .payload-toast-container .payload-toast-item .payload-toast-close-button /* individual toast items will also have these classes depending on the state */ .toast-info .toast-warning .toast-success .toast-error
https://github.com/payloadcms/payload/assets/70709113/da3e732e-aafc-4008-9469-b10f4eb06b35
array
& blocks
& group
fields from sort (#6576) (9f52562)Types are now auto-generated by default.
You can opt-out of this behavior by setting:
buildConfig({ // Rest of config typescript: { autoGenerate: false }, })
Description
Updates the
fields
override in plugin redirects to allow for overriding// before overrides: { fields: [ { type: 'text', name: 'customField', }, ], }, // current overrides: { fields: ({ defaultFields }) => { return [ ...defaultFields, { type: 'text', name: 'customField', }, ] }, },
- This bumps the minimum required node version from node 20.6.0 to node 20.9.0. This is because 20.6.0 breaks type generation due to a CJS node bug, and 20.9.0 is the next v20 LTS version. The minimum node 18 version stays the same (18.20.2)
Fixes #6630
This only applies to you if you using db-postgres and have created the
v2-v3-relationships
migration released in v3.0.0-beta.39 from @payloadcms/db-postgres <= v3.0.0-beta.40.Steps to fix
- Delete the existing v2-v3-relationships migration file.
- If changes were made to your config since the previous migration was made, you will need to revert those by checking out a previous commit in your version control.
- Recreate the migration using
payload migrate:create --file @payloadcms/db-postgres/relationships-v2-v3
to make the migration with the snapshot .json file.
BREAKING:
- This upgrades the required version of lexical from 0.15.0 to 0.16.0. If you are using lexical directly in your project, possibly due to custom features, there might be breaking changes for you. Please consult the lexical 0.16.0 changelog: https://github.com/facebook/lexical/releases/tag/v0.16.0
BREAKING: useEditorFocusProvider has been removed and merged with useEditorConfigContext. You can now find information about the focused editor, parent editors and child editors within useEditorConfigContext
Moves upload
field and relationship
fields with hasMany: false
& relationTo: string
from the many-to-many _rels
join table to simple columns. This only affects Postgres database users.
We have dramatically simplified the storage of simple relationships in relational databases to boost performance and align with more expected relational paradigms. If you are using the beta Postgres adapter, and you need to keep simple relationship data, you'll need to run a migration script that we provide you.
For example, prior to this update, a collection of "posts" with a simple hasMany: false
and relationTo: 'categories'
field would have a posts_rels
table where the category relations would be stored.
This was somewhat unnecessary as simple relations like this can be expressed with a category_id
column which is configured as a foreign key. This also introduced added complexity for dealing directly with the database if all you have are simple relations.
You need to migrate if you are using the beta Postgres database adapter and any of the following applies to you.
upload
fieldhasMany: false
(default) and relationTo
to a single category (has one) relationsEven though the Postgres adapter is in beta, we've prepared a predefined migration that will work out of the box for you to migrate from an earlier version of the adapter to the most recent version easily.
It makes the schema changes in step with actually moving the data from the old locations to the new before adding any null constraints and dropping the old columns and tables.
The steps to preserve your data while making this update are as follows. These steps are the same whether you are moving from Payload v2 to v3 or a previous version of v3 beta to the most recent v3 beta.
Important: during these steps, don't start the dev server unless you have push: false
set on your Postgres adapter.
Always back up your database before performing big changes, especially in production cases.
Before updating to new Payload and Postgres adapter versions, run payload migrate:create
without any other config changes to have a prior snapshot of the schema from the previous adapter version
push
row from your payload_migrations
tableIf you're migrating a dev database where you have the default setting to push database changes directly to your DB, and you need to preserve data in your development database, then you need to delete a dev
migration record from your database.
Connect directly to your database in any tool you'd like and delete the dev push record from the payload_migrations
table using the following SQL statement:
DELETE FROM payload_migrations where batch = -1
Update packages, making sure you have matching versions across all @payloadcms/*
and payload
packages (including @payloadcms/db-postgres
)
Run the following command to create the predefined migration we've provided:
payload migrate:create --file @payloadcms/db-postgres/relationships-v2-v3
Run migrations with the following command:
payload migrate
Assuming the migration worked, you can proceed to commit this change and distribute it to be run on all other environments.
Note that if two servers connect to the same database, only one should be running migrations to avoid transaction conflicts.
Related discussion: https://github.com/payloadcms/payload/discussions/4163
userEmailAlreadyRegistered
translations (#6550) (e0a6db7)Changes the
fields
override for form builder plugin to use a function instead so that we can actually override existing fields which currently will not work.//before fields: [ { name: 'custom', type: 'text', } ] // current fields: ({ defaultFields }) => { return [ ...defaultFields, { name: 'custom', type: 'text', }, ] }
disableListColumn
fields not hidden in table columns (#6445) (bcc506b)BREAKING:
- bumps minimum required next.js version from
14.3.0-canary.68
to15.0.0-rc.0
- bumps minimum required react and react-dom versions to
19.0.0
(19.0.0-rc-f994737d14-20240522
should be used)@types/react
and@types/react-dom
have to be bumped tonpm:types-react@19.0.0-beta.2
using overrides and pnpm overrides, if you want correct types. You can find an example of this here: https://github.com/payloadcms/payload/pull/6429/files#diff-10cb9e57a77733f174ee2888587281e94c31f79e434aa3f932a8ec72fa7a5121L32Issues
- Bunch of todos for our react-select package which is having type issues. Works fine, just type issues. Their type defs are importing JSX in a weird way, we likely just have to wait until they fix them in a future update.
Description
Renames the
Save
toSaveButton
, etc. to match the already established convention of thePreviewButton
, etc. This matches the imports with their respective component and type names, and also gives these components more context to the developer whenever they're rendered, i.e. its clearly just a button and not an entire block or complex component.BREAKING:
Import paths for these components have changed, if you were previously importing these components into your own projects to customize, change the import paths accordingly:
Old:
import { PublishButton } from '@payloadcms/ui/elements/Publish' import { SaveButton } from '@payloadcms/ui/elements/Save' import { SaveDraftButton } from '@payloadcms/ui/elements/SaveDraft'
New:
import { PublishButton } from '@payloadcms/ui/elements/PublishButton' import { SaveButton } from '@payloadcms/ui/elements/SaveButton' import { SaveDraftButton } from '@payloadcms/ui/elements/SaveDraftButton'
- [x] I have read and understand the CONTRIBUTING.md document in this repository.
Change the exports of DefaultListView and DefaultEditView to be renamed without "Default" as ListView
// before import { DefaultEditView } from '@payloadcms/next/views' import { DefaultListView } from '@payloadcms/next/views' // after import { EditView } from '@payloadcms/next/views' import { ListView } from '@payloadcms/next/views'
BREAKING:
- The minimum required next version is now 14.3.0-canary.68. This is because we are migrating away from the deprecated experimental.serverComponentsExternalPackages next config key to experimental.serverExternalPackages, which is not available in older next canaries
- The minimum
react
andreact-dom
versions have been bumped to ^18.2.0 or ^19.0.0. This matches the minimum react version recommended by next
next: removes initPage export from barrel file (#6403) (553bb4b)
replaces admin.meta.ogImage with admin.meta.openGraph.images (#6227) (9556d1b)
remove unused staticOptions config on uploads (#6378) (a6bf058)
Removes the unused
staticOptions
on upload config, it was previously typed to express configuration and is unused anywhere in the codebase
BREAKING: This upgrades all lexical packages from 0.14.5 to 0.15.0. If there are any breaking changes within lexical, this could break your project if you use lexical APIs directly (e.g. in custom features). We have not noticed any breaking changes within core. Please consult their changelog: https://github.com/facebook/lexical/releases/tag/v0.15.0"