From abe4cc87ca0b351a805ae7382b31fd5cdaeea0ed Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Sat, 16 Nov 2024 23:34:14 -0500 Subject: [PATCH] docs: updates migration guide (#9251) Documents more breaking changes within the migration guide, improves overview, reorganizes everything, adds section headings, table of contents, and more. --- docs/migration-guide/overview.mdx | 716 +++++++++++++++++++----------- 1 file changed, 461 insertions(+), 255 deletions(-) diff --git a/docs/migration-guide/overview.mdx b/docs/migration-guide/overview.mdx index 2dead9ab4..55247d833 100644 --- a/docs/migration-guide/overview.mdx +++ b/docs/migration-guide/overview.mdx @@ -1,15 +1,35 @@ -# 🚧 **DRAFT:** 3.0 Migration Guide / Breaking Changes +# Payload 2.0 to 3.0 Migration Guide -> [!IMPORTANT] -> This document will continue to be updated and cleaned up until the 3.0 release. +Payload 3.0 completely replatforms the Admin Panel from a React Router single-page application onto the Next.js App Router with full support for React Server Components. This change completely separates Payload "core" from its rendering and HTTP layers, making it truly Node-safe and portable. ## What has changed? -The core logic and principles of Payload remain the same from 2.0 to 3.0, with the majority of changes affecting specifically the HTTP layer and the Admin Panel, which is now built upon Next.js. With this change, your entire application can be served within a single repo. Payload endpoints are now opened directly within your own Next.js application, right alongside your frontend. All Payload APIs remain exactly the same (with a few new features), and the Payload Config is generally the same, with the breaking changes detailed below. +The core logic and principles of Payload remain the same from 2.0 to 3.0, with the majority of changes affecting specifically the HTTP layer and the Admin Panel, which is now built upon Next.js. With this change, your entire application can be served within a single repo, with Payload endpoints are now opened within your own Next.js application, directly alongside your frontend. Payload is still headless, you will still be able to leverage it completely headlessly just as you do now with Sveltekit, etc. All Payload APIs remain exactly the same (with a few new features), and the Payload Config is generally the same, with the breaking changes detailed below. -Payload is still headless, you will still be able to leverage it completely headlessly just as you do now with Sveltekit, etc. In fact, Payload itself is now _truly_ portable because it is fully Node.js compatible and completely separate from the HTTP layer. The entire Payload suite of packages has been modularized to make this possible. +### Table of Contents -## To migrate from Payload 2.0 to 3.0: +All breaking changes are listed below. If you encounter changes that are not expicitly listed here, please consider contributing to this documentation by submitting a PR. + +- [Installation](#installation) +- [Breaking Changes](#breaking-changes) +- [Custom Components](#custom-components) +- [Endpoints](#endpoints) +- [React Hooks](#react-hooks) +- [Types](#types) +- [Email Adapters](#email-adapters) +- [Plugins](#plugins) + +## Installation + +Payload 3.0 requires a set of auto-generated files that you will need to bring into your existing project. The easiest way of aquiring these is by initializing a new project via `create-payload-app`, then replace the provided Payload Config with your own. + +```bash + npx create-payload-app@beta +``` + +For more details, see the [Documentation](https://payloadcms.com/docs/getting-started/installation). + +## Breaking Changes 1. Delete the `admin.bundler` property from your Payload Config. Payload no longer bundles the Admin Panel. Instead, we rely directly on Next.js for bundling. @@ -39,6 +59,23 @@ Payload is still headless, you will still be able to leverage it completely head }) ``` +1. Environment variables prefixed with `PAYLOAD_PUBLIC` will no longer be available on the client. In order to access them on the client, those will now have to be prefixed with `NEXT_PUBLIC` instead. + + ```diff + 'use client' + - const var = process.env.PAYLOAD_PUBLIC_MY_ENV_VAR + + const var = process.env.NEXT_PUBLIC_MY_ENV_VAR + ``` + + For more details, see the [Documentation](https://payloadcms.com/docs/beta/configuration/environment-vars). + +1. The `req` object used to extend the [Express Request](https://expressjs.com/), but now extends the [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). You may need to update your code accordingly to reflect this change. For example: + + ```diff + - req.headers['content-type'] + + req.headers.get('content-type') + ``` + 1. The `admin.css` and `admin.scss` properties in the Payload Config have been removed. ```diff @@ -133,227 +170,42 @@ Payload is still headless, you will still be able to leverage it completely head } ``` -1. All Payload React components have been moved from the `payload` package to `@payloadcms/ui`. If you were previously importing components into your app from the `payload` package, for example to create Custom Components, you will need to change your import paths: +1. Fields with `unique: true` now automatically be appended with “- Copy” through the new `admin.beforeDuplicate` field hooks (see previous bullet). + +1. The `upload.staticDir` property must now be an absolute path. Before it would attempt to use the location of the Payload Config and merge the relative path set for staticDir. ```diff - - import { TextField, useField, etc. } from 'payload' - + import { TextField, useField, etc. } from '@payloadcms/ui' - ``` - *Note: for brevity, not _all_ modules are listed here* + // collections/Media.ts + import type { CollectionConfig } from 'payload' + import path from 'path' + + import { fileURLToPath } from 'url' -1. The `BlockField` and related types have been renamed to `BlocksField` for semantic accuracy. + + const filename = fileURLToPath(import.meta.url) + + const dirname = path.dirname(filename) - ```diff - - import type { BlockField, BlockFieldProps } from 'payload' - + import type { BlocksField, BlocksFieldProps } from 'payload' - ``` - -1. All Custom Components are now defined as _file paths_ instead of direct imports. If you are using Custom Components in your Payload Config, remove the imported module and point to the file's path instead: - - ```diff - import { buildConfig } from 'payload' - - import { MyComponent } from './src/components/Logout' - - const config = buildConfig({ - // ... - admin: { - components: { - logout: { - - Button: MyComponent, - + Button: '/src/components/Logout#MyComponent' - } - } + export const MediaCollection: CollectionConfig = { + slug: 'media', + upload: { + - staticDir: path.resolve(__dirname, './uploads'), + + staticDir: path.resolve(dirname, '../uploads'), }, - }) - ``` - - For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#component-paths). - -1. All Custom Components are now server-rendered by default, and therefore, cannot use state or hooks directly. If you’re using Custom Components in your app that requires state or hooks, add the `'use client'` directive at the top of the file. - - ```diff - // components/MyClientComponent.tsx - + 'use client' - import React, { useState } from 'react' - - export const MyClientComponent = () => { - const [state, setState] = useState() - - return ( -
- {state} -
- ) } ``` - For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#client-components). - -1. The `custom` property in the Payload Config, i.e. Collections, Globals, and Fields is now **server only** and will **not** appear in the client-side bundle. To add custom properties to the client bundle, use the new `admin.custom` property, which will be available on _both_ the server and the client. +1. The `upload.staticURL` property has been removed. If you were using this format URLs when using an external provider, you can leverage the `generateFileURL` functions in order to do the same. ```diff - // payload.config.ts - import { buildConfig } from 'payload' - - export default buildConfig({ - custom: { - someProperty: 'My Server Prop' // Now server only! - }, - admin: { - + custom: { - + name: 'My Client Prop' // Available in server AND client - + } - }, - }) - ``` - -1. The `useTitle` hook has been consolidated into the `useDocumentInfo` hook. Instead, you can get title directly from document info context: - - ```diff - 'use client' - - import { useTitle } from 'payload' - + import { useDocumentInfo } from '@payloadcms/ui' - - export const MyComponent = () => { - - const title = useTitle() - + const { title } = useDocumentInfo() - - // ... - } - ``` - -1. The `Fields` type was renamed to `FormState` for improved semantics. If you were previously importing this type in your own application, simply change the import name: - - ```diff - - import type { Fields } from 'payload' - + import type { FormState } from 'payload' - ``` - -1. The `useDocumentInfo` hook no longer returns `collection` or `global`. Instead, various properties of the config are passed, like `collectionSlug` and `globalSlug`. You can use these to access a client-side config, if needed, through the `useConfig` hook (see next bullet). - - ```diff - 'use client' - import { useDocumentInfo } from '@payloadcms/ui' - - export const MyComponent = () => { - const { - - collection, - - global, - + collectionSlug, - + globalSlug - } = useDocumentInfo() - - // ... - } - ``` - -1. The `useConfig` hook now returns a `ClientConfig` and not a `SanitizedConfig`. This is because the config itself is not serializable and so it is not able to be thread through to the client. This means that all non-serializable props have been omitted from the Client Config, such as `db`, `bundler`, etc. - - ```diff - 'use client' - - import { useConfig } from 'payload' - + import { useConfig } from '@payloadcms/ui' - - export const MyComponent = () => { - - const config = useConfig() // used to be a 'SanitizedConfig' - + const { config } = useConfig() // now is a 'ClientConfig' - - // ... - } - ``` - - For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#accessing-the-payload-config). - -1. The args of the `admin.livePreview.url` function have changed. It no longer receives `documentInfo` as an arg, and instead, now has `collectionConfig` and `globalConfig`. - - ```diff - // payload.config.ts - import { buildConfig } from 'payload' - - export default buildConfig({ - // ... - admin: { - // ... - livePreview: ({ - - documentInfo, - + collectionConfig, - + globalConfig - }) => '' - } - }) - ``` - -1. The `href` and `isActive` functions on View Tabs no longer includes the `match` or `location` arguments. This is is a property specific to React Router, not Next.js. If you need to do URL matching similar to this, use a custom tab that fires of some hooks, i.e. `usePathname()` and run it through your own utility functions: - - ```diff - // collections/Posts.ts + // collections/Media.ts import type { CollectionConfig } from 'payload' - export const PostsCollection: CollectionConfig = { - slug: 'posts', - admin: { - components: { - views: { - - Edit: { - - Tab: { - - isActive: ({ href, location, match }) => true, - - href: ({ href, location, match }) => '' - - }, - - }, - + edit: { - + tab: { - + isActive: ({ href }) => true, - + href: ({ href }) => '' - + Component: './path/to/CustomComponent.tsx', // Or use a Custom Component - + } - + }, - }, - }, + export const MediaCollection: CollectionConfig = { + slug: 'media', + upload: { + - staticURL: '', }, } ``` -1. The `admin.components.views[key].Tab.pillLabel` has been replaced with `admin.components.views[key].tab.Pill`: - - ```diff - // collections/Posts.ts - import type { CollectionConfig } from 'payload' - - export const PostsCollection: CollectionConfig = { - slug: 'posts', - admin: { - components: { - - views: { - - edit: { - - Tab: { - - pillLabel: 'Hello, world!', - - }, - - }, - + Edit: { - + tab: { - + Pill: './path/to/CustomPill.tsx', - + } - + }, - }, - }, - }, - } - ``` - -1. Unique fields will now automatically be appended with “- Copy” through the new `admin.beforeDuplicate` field hooks (detailed aboved). - -1. The `useCollapsible` hook has had slight changes to its property names. `collapsed` is now `isCollapsed` and `withinCollapsible` is now `isWithinCollapsible`. - - ```diff - 'use client' - import { useCollapsible } from '@payloadcms/ui' - - export const MyComponent = () => { - - const { collapsed, withinCollapsible } = useCollapsible() - + const { isCollapsed, isWithinCollapsible } = useCollapsible() - } - ``` - 1. The `admin.favicon` property is now `admin.icons` and the types have changed: ```diff @@ -395,6 +247,25 @@ Payload is still headless, you will still be able to leverage it completely head For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/metadata#open-graph). +1. The args of the `admin.livePreview.url` function have changed. It no longer receives `documentInfo` as an arg, and instead, now has `collectionConfig` and `globalConfig`. + + ```diff + // payload.config.ts + import { buildConfig } from 'payload' + + export default buildConfig({ + // ... + admin: { + // ... + livePreview: ({ + - documentInfo, + + collectionConfig, + + globalConfig + }) => '' + } + }) + ``` + 1. The `admin.logoutRoute` and `admin.inactivityRoute` properties have been consolidated into a single `admin.routes` property. To migrate, simply move those two keys as follows: ```diff @@ -414,29 +285,307 @@ Payload is still headless, you will still be able to leverage it completely head }) ``` -1. Environment variables prefixed with `PAYLOAD_PUBLIC` will no longer be available on the client. In order to access them on the client, those will now have to be prefixed with `NEXT_PUBLIC` instead. +1. The `custom` property in the Payload Config, i.e. Collections, Globals, and Fields is now **server only** and will **not** appear in the client-side bundle. To add custom properties to the client bundle, use the new `admin.custom` property, which will be available on _both_ the server and the client. ```diff - 'use client' - - const var = process.env.PAYLOAD_PUBLIC_MY_ENV_VAR - + const var = process.env.NEXT_PUBLIC_MY_ENV_VAR + // payload.config.ts + import { buildConfig } from 'payload' + + export default buildConfig({ + custom: { + someProperty: 'My Server Prop' // Now server only! + }, + admin: { + + custom: { + + name: 'My Client Prop' // Available in server AND client + + } + }, + }) ``` - For more details, see the [Documentation](https://payloadcms.com/docs/beta/configuration/environment-vars). +## Custom Components -1. The `useTranslation` hook no longer takes any options, any translations using shorthand accessors will need to use the entire `group:key` +1. All Payload React components have been moved from the `payload` package to `@payloadcms/ui`. If you were previously importing components into your app from the `payload` package, for example to create Custom Components, you will need to change your import paths: ```diff - 'use client' - - import { useTranslation } from 'payload' - + import { useTranslation } from '@payloadcms/ui' + - import { TextField, useField, etc. } from 'payload' + + import { TextField, useField, etc. } from '@payloadcms/ui' + ``` + *Note: for brevity, not _all_ modules are listed here* - export const MyComponent = () => { - - const { i18n, t } = useTranslation('general') - + const { i18n, t } = useTranslation() +1. All Custom Components are now defined as _file paths_ instead of direct imports. If you are using Custom Components in your Payload Config, remove the imported module and point to the file's path instead: - - return

{t('cancel')}

- + return

{t('general:cancel')}

+ ```diff + import { buildConfig } from 'payload' + - import { MyComponent } from './src/components/Logout' + + const config = buildConfig({ + // ... + admin: { + components: { + logout: { + - Button: MyComponent, + + Button: '/src/components/Logout#MyComponent' + } + } + }, + }) + ``` + + For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#component-paths). + +1. All Custom Components are now server-rendered by default, and therefore, cannot use state or hooks directly. If you’re using Custom Components in your app that requires state or hooks, add the `'use client'` directive at the top of the file. + + ```diff + // components/MyClientComponent.tsx + + 'use client' + import React, { useState } from 'react' + + export const MyClientComponent = () => { + const [state, setState] = useState() + + return ( +
+ {state} +
+ ) + } + ``` + + For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#client-components). + +1. The `admin.description` property within Collection, Globals, and Fields no longer accepts a React Component. Instead, you must define it as a Custom Component. + + 1. For Collections, use the `admin.components.edit.Description` key: + + ```diff + // collections/Posts.ts + import type { CollectionConfig } from 'payload' + - import { MyCustomDescription } from '../components/MyCustomDescription' + + export const PostsCollection: CollectionConfig = { + slug: 'posts', + admin: { + - description: MyCustomDescription, + + components: { + + edit: { + + Description: 'path/to/MyCustomDescription' + + } + + } + } + } + ``` + + 2. For Globals, use the `admin.components.elements.Description` key: + + ```diff + // globals/Site.ts + import type { GlobalConfig } from 'payload' + - import { MyCustomDescription } from '../components/MyCustomDescription' + + export const SiteGlobal: GlobalConfig = { + slug: 'site', + admin: { + - description: MyCustomDescription, + + components: { + + elements: { + + Description: 'path/to/MyCustomDescription' + + } + + } + } + } + ``` + + 3. For Fields, use the `admin.components.Description` key: + + ```diff + // fields/MyField.ts + import type { FieldConfig } from 'payload' + - import { MyCustomDescription } from '../components/MyCustomDescription' + + export const MyField: FieldConfig = { + type: 'text', + admin: { + - description: MyCustomDescription, + + components: { + + Description: 'path/to/MyCustomDescription' + + } + } + } + ``` + +1. Array Field row labels and Collapsible Field label now _only_ accepts a React Component, and no longer accepts a plain string or record: + + ```diff + // file: Collection.tsx + import type { CollectionConfig } from 'payload' + - import { MyCustomRowLabel } from './components/MyCustomRowLabel.tsx' + + export const MyCollection: CollectionConfig = { + slug: 'my-collection', + fields: [ + { + name: 'my-array', + type: 'array', + admin: { + components: { + - RowLabel: 'My Array Row Label, + + RowLabel: './components/RowLabel.ts' + } + }, + fields: [...] + }, + { + name: 'my-collapsible', + type: 'collapsible', + admin: { + components: { + - Label: 'My Collapsible Label', + + Label: './components/RowLabel.ts' + } + }, + fields: [...] + } + ] + } + ``` + +1. All default view keys are now camelcase: + + For example, for Root Views: + + ```diff + // file: payload.config.ts + + import { buildConfig } from 'payload' + + export default buildConfig({ + admin: { + views: { + - Account: ... + + account: ... + } + }) + ``` + + Or Document Views: + + ```diff + // file: Collection.tsx + + import type { CollectionConfig } from 'payload' + + export const MyCollection: CollectionConfig = { + slug: 'my-collection', + admin: { + views: { + - Edit: { + - Default: ... + - } + + edit: { + + default: ... + + } + } + } + } + ``` + +1. Custom Views within the config no longer accept React Components directly, instead, you must use their `Component` property: + + ```diff + // file: Collection.tsx + import type { CollectionConfig } from 'payload' + - import { MyCustomView } from './components/MyCustomView.tsx' + + export const MyCollection: CollectionConfig = { + slug: 'my-collection', + admin: { + views: { + - Edit: MyCustomView + + edit: { + + Component: './components/MyCustomView.tsx' + + } + } + } + } + ``` + + This also means that Custom Root Views are no longer defined on the `edit` key. Instead, use the new `views.root` key: + + ```diff + // file: Collection.tsx + import type { CollectionConfig } from 'payload' + - import { MyCustomRootView } from './components/MyCustomRootView.tsx' + + export const MyCollection: CollectionConfig = { + slug: 'my-collection', + admin: { + views: { + - Edit: MyCustomRootView + edit: { + + root: { + + Component: './components/MyCustomRootView.tsx' + + } + } + } + } + } + ``` + +1. The `href` and `isActive` functions on View Tabs no longer includes the `match` or `location` arguments. This is is a property specific to React Router, not Next.js. If you need to do URL matching similar to this, use a custom tab that fires of some hooks, i.e. `usePathname()` and run it through your own utility functions: + + ```diff + // collections/Posts.ts + import type { CollectionConfig } from 'payload' + + export const PostsCollection: CollectionConfig = { + slug: 'posts', + admin: { + components: { + views: { + - Edit: { + - Tab: { + - isActive: ({ href, location, match }) => true, + - href: ({ href, location, match }) => '' + - }, + - }, + + edit: { + + tab: { + + isActive: ({ href }) => true, + + href: ({ href }) => '' + + Component: './path/to/CustomComponent.tsx', // Or use a Custom Component + + } + + }, + }, + }, + }, + } + ``` + +1. The `admin.components.views[key].Tab.pillLabel` has been replaced with `admin.components.views[key].tab.Pill`: + + ```diff + // collections/Posts.ts + import type { CollectionConfig } from 'payload' + + export const PostsCollection: CollectionConfig = { + slug: 'posts', + admin: { + components: { + - views: { + - Edit: { + - Tab: { + - pillLabel: 'Hello, world!', + - }, + - }, + + edit: { + + tab: { + + Pill: './path/to/CustomPill.tsx', + + } + + }, + }, + }, + }, } ``` @@ -467,7 +616,9 @@ Payload is still headless, you will still be able to leverage it completely head } ``` -1. All other endpoint handlers have changed. The args no longer include `res`, and `next`, and the return type now expects a valid HTTP [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) instead of `res.json`, `res.send`, etc.: +## Endpoints + +1. All endpoint handlers have changed. The args no longer include `res`, and `next`, and the return type now expects a valid HTTP [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) instead of `res.json`, `res.send`, etc.: ```diff // collections/Posts.ts @@ -542,47 +693,102 @@ Payload is still headless, you will still be able to leverage it completely head } ``` -1. The `req` object used to extend the [Express Request](https://expressjs.com/), but now extends the [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). You may need to update your code accordingly to reflect this change. For example: +## React Hooks + +1. The `useTitle` hook has been consolidated into the `useDocumentInfo` hook. Instead, you can get title directly from document info context: ```diff - - req.headers['content-type'] - + req.headers.get('content-type') - ``` + 'use client' + - import { useTitle } from 'payload' + + import { useDocumentInfo } from '@payloadcms/ui' -1. `staticDir` must now be an absolute path. Before it would attempt to use the location of the Payload Config and merge the relative path set for staticDir. + export const MyComponent = () => { + - const title = useTitle() + + const { title } = useDocumentInfo() - ```diff - // collections/Media.ts - import type { CollectionConfig } from 'payload' - import path from 'path' - + import { fileURLToPath } from 'url' - - + const filename = fileURLToPath(import.meta.url) - + const dirname = path.dirname(filename) - - export const MediaCollection: CollectionConfig = { - slug: 'media', - upload: { - - staticDir: path.resolve(__dirname, './uploads'), - + staticDir: path.resolve(dirname, '../uploads'), - }, + // ... } ``` -1. `staticURL` has been removed. If you were using this format URLs when using an external provider, you can leverage the `generateFileURL` functions in order to do the same. +1. The `useDocumentInfo` hook no longer returns `collection` or `global`. Instead, various properties of the config are passed, like `collectionSlug` and `globalSlug`. You can use these to access a client-side config, if needed, through the `useConfig` hook (see next bullet). ```diff - // collections/Media.ts - import type { CollectionConfig } from 'payload' + 'use client' + import { useDocumentInfo } from '@payloadcms/ui' - export const MediaCollection: CollectionConfig = { - slug: 'media', - upload: { - - staticURL: '', - }, + export const MyComponent = () => { + const { + - collection, + - global, + + collectionSlug, + + globalSlug + } = useDocumentInfo() + + // ... } ``` +1. The `useConfig` hook now returns a `ClientConfig` and not a `SanitizedConfig`. This is because the config itself is not serializable and so it is not able to be thread through to the client. This means that all non-serializable props have been omitted from the Client Config, such as `db`, `bundler`, etc. + + ```diff + 'use client' + - import { useConfig } from 'payload' + + import { useConfig } from '@payloadcms/ui' + + export const MyComponent = () => { + - const config = useConfig() // used to be a 'SanitizedConfig' + + const { config } = useConfig() // now is a 'ClientConfig' + + // ... + } + ``` + + For more details, see the [Documentation](https://payloadcms.com/docs/beta/admin/components#accessing-the-payload-config). + +1. The `useCollapsible` hook has had slight changes to its property names. `collapsed` is now `isCollapsed` and `withinCollapsible` is now `isWithinCollapsible`. + + ```diff + 'use client' + import { useCollapsible } from '@payloadcms/ui' + + export const MyComponent = () => { + - const { collapsed, withinCollapsible } = useCollapsible() + + const { isCollapsed, isWithinCollapsible } = useCollapsible() + } + ``` + +1. The `useTranslation` hook no longer takes any options, any translations using shorthand accessors will need to use the entire `group:key` + + ```diff + 'use client' + - import { useTranslation } from 'payload' + + import { useTranslation } from '@payloadcms/ui' + + export const MyComponent = () => { + - const { i18n, t } = useTranslation('general') + + const { i18n, t } = useTranslation() + + - return

{t('cancel')}

+ + return

{t('general:cancel')}

+ } + ``` + +## Types + +1. The `Fields` type was renamed to `FormState` for improved semantics. If you were previously importing this type in your own application, simply change the import name: + + ```diff + - import type { Fields } from 'payload' + + import type { FormState } from 'payload' + ``` + +1. The `BlockField` and related types have been renamed to `BlocksField` for semantic accuracy. + + ```diff + - import type { BlockField, BlockFieldProps } from 'payload' + + import type { BlocksField, BlocksFieldProps } from 'payload' + ``` + ## Email Adapters Email functionality has been abstracted out into email adapters. @@ -647,7 +853,7 @@ export default buildConfig({ - Now only available if using custom server and using express or similar -# Plugins +## Plugins 1. *All* plugins have been standardized to use _named exports_ (as opposed to default exports). Most also have a suffix of `Plugin` to make it clear what is being imported.