Compare commits
66 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4375a33706 | ||
|
|
51056769e5 | ||
|
|
abf6e9aa6b | ||
|
|
5ffc5a1248 | ||
|
|
ed73dedd14 | ||
|
|
6b7ec6cbf2 | ||
|
|
35eb16bbec | ||
|
|
f47d6cb23c | ||
|
|
c34aa86da1 | ||
|
|
ae8a5a9cb8 | ||
|
|
d8d5a44895 | ||
|
|
377a478fc2 | ||
|
|
0b2be54011 | ||
|
|
cae423fd6b | ||
|
|
d63bc5c20c | ||
|
|
1a9c63bf26 | ||
|
|
f01fc584ed | ||
|
|
cd1a2147be | ||
|
|
2920a5d0a8 | ||
|
|
ccbaee43cc | ||
|
|
3a0ca12881 | ||
|
|
9096b746d3 | ||
|
|
fce545bed6 | ||
|
|
2396a70e45 | ||
|
|
776e3f7069 | ||
|
|
314488e55a | ||
|
|
effba3e45b | ||
|
|
7f753fb3b5 | ||
|
|
e782d99429 | ||
|
|
b0e933886e | ||
|
|
9b850e0a01 | ||
|
|
e036d4efab | ||
|
|
8f977b9548 | ||
|
|
733370655f | ||
|
|
238b69278b | ||
|
|
39868426b6 | ||
|
|
d66b3486c4 | ||
|
|
ead7d953f3 | ||
|
|
ac1820dca6 | ||
|
|
a9cafa4fce | ||
|
|
d9e11b6fab | ||
|
|
c3661595cc | ||
|
|
8773e3a7e5 | ||
|
|
6ba619f6f4 | ||
|
|
62b13329fd | ||
|
|
d01fb804a4 | ||
|
|
285835f23a | ||
|
|
b5ac0bd365 | ||
|
|
aef2a52cea | ||
|
|
bc98567f41 | ||
|
|
317bc070e4 | ||
|
|
5a994d9739 | ||
|
|
3ddc2a0e83 | ||
|
|
2c4da93b28 | ||
|
|
cf50eabbab | ||
|
|
bf374a23ab | ||
|
|
223d726280 | ||
|
|
a680e687b5 | ||
|
|
b7d5a6a2a2 | ||
|
|
040893ff22 | ||
|
|
cedd916816 | ||
|
|
45871489d0 | ||
|
|
35a5d0cb3c | ||
|
|
47ee40a3f4 | ||
|
|
25968d43c2 | ||
|
|
9e76c8f4e3 |
1
.github/workflows/main.yml
vendored
1
.github/workflows/main.yml
vendored
@@ -503,6 +503,7 @@ jobs:
|
||||
|
||||
generated-templates:
|
||||
needs: build
|
||||
if: false # Needs to pull in tgz files from build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -12,6 +12,11 @@ test-results
|
||||
.localstack
|
||||
.turbo
|
||||
|
||||
meta_client.json
|
||||
meta_server.json
|
||||
meta_index.json
|
||||
meta_shared.json
|
||||
|
||||
.turbo
|
||||
|
||||
# Ignore test directory media folder/files
|
||||
|
||||
1
.idea/payload.iml
generated
1
.idea/payload.iml
generated
@@ -74,6 +74,7 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/translations/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/.swc" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/.turbo" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages/ui/dist" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
import configPromise from '@payload-config'
|
||||
import { RootLayout } from '@payloadcms/next/layouts'
|
||||
// import '@payloadcms/ui/styles.css' // Uncomment this line if `@payloadcms/ui` in `tsconfig.json` points to `/ui/dist` instead of `/ui/src`
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import React from 'react'
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ If a Collection supports [`Authentication`](/docs/authentication/overview), the
|
||||
**Example Collection config:**
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import type { CollectionConfig } from 'payload';
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: "posts",
|
||||
@@ -86,7 +86,7 @@ Read access functions can return a boolean result or optionally return a [query
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { Access } from 'payload/config'
|
||||
import type { Access } from 'payload'
|
||||
|
||||
const canReadPage: Access = ({ req: { user } }) => {
|
||||
// allow authenticated users
|
||||
@@ -118,7 +118,7 @@ Update access functions can return a boolean result or optionally return a [quer
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { Access } from 'payload/config'
|
||||
import type { Access } from 'payload'
|
||||
|
||||
const canUpdateUser: Access = ({ req: { user }, id }) => {
|
||||
// allow users with a role of 'admin'
|
||||
@@ -144,7 +144,7 @@ Similarly to the Update function, returns a boolean or a [query constraint](/doc
|
||||
**Example:**
|
||||
|
||||
```ts
|
||||
import { Access } from 'payload/config'
|
||||
import type { Access } from 'payload'
|
||||
|
||||
const canDeleteCustomer: Access = async ({ req, id }) => {
|
||||
if (!id) {
|
||||
|
||||
@@ -19,7 +19,7 @@ Field Access Control is specified with functions inside a field's config. All fi
|
||||
**Example Collection config:**
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import { CollectionConfig } from 'payload';
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
|
||||
@@ -18,7 +18,7 @@ You can define Global-level Access Control within each Global's `access` propert
|
||||
**Example Global config:**
|
||||
|
||||
```ts
|
||||
import { GlobalConfig } from 'payload/types'
|
||||
import { GlobalConfig } from 'payload'
|
||||
|
||||
const Header: GlobalConfig = {
|
||||
slug: 'header',
|
||||
|
||||
@@ -6,80 +6,137 @@ desc: Fully customize your Admin Panel by swapping in your own React components.
|
||||
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
The [Payload Admin](./overview) panel is designed to be as minimal and straightforward as possible to allow easy customization and control. In order for Payload to support this level of customization without introducing versioning or future-proofing issues, Payload provides a pattern for you to supply your own React components via your Payload config.
|
||||
The [Payload Admin Panel](./overview) is designed to be as minimal and straightforward as possible to allow for both easy customization and full control over the UI. In order for Payload to support this level of customization, Payload provides a pattern for you to supply your own React components through your [Payload Config](../configuration/overview).
|
||||
|
||||
<Banner type="warning">
|
||||
All Custom Components in the Admin Panel are [React Server Components](https://react.dev/reference/rsc/server-components). This means they are rendered on the server
|
||||
</Banner>
|
||||
|
||||
To swap in your own React component, first, consult the list of available components below. Determine the scope that corresponds to what you are trying to accomplish, and then author your React component accordingly.
|
||||
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, with the exception of [Custom Providers](#custom-providers). This enables the use of the [Local API](../local-api) directly in the front-end. Custom Components are available for nearly every part of the Admin Panel for extreme granularity and control.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
Custom components will automatically be provided with all props that the default component
|
||||
normally accepts.
|
||||
<strong>Note:</strong>
|
||||
Client Components continue to be fully supported. To use Client Components in your app, simply include the `use client` directive. Payload will automatically detect and remove all default [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types) before rendering your component. [More details](#client-components).
|
||||
</Banner>
|
||||
|
||||
## Base Component Overrides
|
||||
To swap in your own Custom Component, consult the list of available components below. Determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-components) accordingly.
|
||||
|
||||
You can override a set of admin panel-wide components by providing a component to your base Payload config's `admin.components` property. The following options are available:
|
||||
There are four main types of Custom Components in Payload:
|
||||
|
||||
| Path | Description |
|
||||
|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`Nav`** | Contains the sidebar / mobile menu in its entirety. |
|
||||
| **`BeforeNavLinks`** | Array of components to inject into the built-in Nav, _before_ the links themselves. |
|
||||
| **`AfterNavLinks`** | Array of components to inject into the built-in Nav, _after_ the links. |
|
||||
| **`BeforeDashboard`** | Array of components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
|
||||
| **`AfterDashboard`** | Array of components to inject into the built-in Dashboard, _after_ the default dashboard contents. [Demo](https://github.com/payloadcms/payload/tree/main/test/admin/components/AfterDashboard/index.tsx) |
|
||||
| **`BeforeLogin`** | Array of components to inject into the built-in Login, _before_ the default login form. |
|
||||
| **`AfterLogin`** | Array of components to inject into the built-in Login, _after_ the default login form. |
|
||||
| **`logout.Button`** | A custom React component. |
|
||||
| **`graphics.Icon`** | Used as a graphic within the `Nav` component. Often represents a condensed version of a full logo. |
|
||||
| **`graphics.Logo`** | The full logo to be used in contexts like the `Login` view. |
|
||||
| **`providers`** | Define your own provider components that will wrap the [Admin Panel](./overview). [More](#custom-providers) |
|
||||
| **`actions`** | Array of custom components to be rendered in the [Admin Panel](./overview) header, providing additional interactivity and functionality. |
|
||||
| **`views`** | Override or create new [Views](./views) within the [Admin Panel](./overview). |
|
||||
- [Root Components](#custom-root-components)
|
||||
- [Collection Components](#custom-collection-components)
|
||||
- [Global Components](#custom-global-components)
|
||||
- [Field Components](./fields)
|
||||
|
||||
Here is a full example showing how to swap some of these components for your own.
|
||||
## Custom Root Components
|
||||
|
||||
`payload.config.js`
|
||||
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo. You can override Root Components through the `admin.components` property of the [Payload Config](../getting-started/overview).
|
||||
|
||||
Here is an example showing what it might look like to swap out Root Components for your own. See [Building Custom Components](#building-custom-components) for exact details on how to build them:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
import {
|
||||
MyCustomNav,
|
||||
MyCustomLogo,
|
||||
MyCustomIcon,
|
||||
MyCustomAccount,
|
||||
MyCustomDashboard,
|
||||
MyProvider,
|
||||
MyCustomAdminAction,
|
||||
} from './customComponents'
|
||||
import { MyCustomLogo } from './MyCustomLogo'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
Nav: MyCustomNav,
|
||||
graphics: {
|
||||
Icon: MyCustomIcon,
|
||||
Logo: MyCustomLogo,
|
||||
Logo: MyCustomLogo, // highlight-line
|
||||
},
|
||||
actions: [MyCustomAdminAction],
|
||||
views: {
|
||||
Account: MyCustomAccount,
|
||||
Dashboard: MyCustomDashboard,
|
||||
},
|
||||
providers: [MyProvider],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Collections
|
||||
The following options are available:
|
||||
|
||||
You can override components on a collection-by-collection basis via the `admin.components` property.
|
||||
| Path | Description |
|
||||
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Nav`** | Contains the sidebar / mobile menu in its entirety. |
|
||||
| **`BeforeNavLinks`** | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. |
|
||||
| **`AfterNavLinks`** | An array of Custom Components to inject into the built-in Nav, _after_ the links. |
|
||||
| **`BeforeDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
|
||||
| **`AfterDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. |
|
||||
| **`BeforeLogin`** | An array of Custom Components to inject into the built-in Login, _before_ the default login form. |
|
||||
| **`AfterLogin`** | An array of Custom Components to inject into the built-in Login, _after_ the default login form. |
|
||||
| **`logout.Button`** | The button displayed in the sidebar that logs the user out. |
|
||||
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
|
||||
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
|
||||
| **`providers`** | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire [Admin Panel](./overview). [More details](#custom-providers). |
|
||||
| **`actions`** | An array of Custom Components to be rendered in the header of the [Admin Panel](./overview), providing additional interactivity and functionality. |
|
||||
| **`views`** | Override or create new views within the [Admin Panel](./overview). [More details](./views). |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong> You can also use the `admin.components` property on any Collection Config or Global Config to set [Custom Collection Components](#custom-collection-components) or [Custom Global Components](#custom-global-components).
|
||||
</Banner>
|
||||
|
||||
### Custom Providers
|
||||
|
||||
You can add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) to any Payload app through Custom Providers. As you add more and more Custom Components to your [Admin Panel](./overview), this is a great way to share state across all of them.
|
||||
|
||||
To do this, add `admin.components.providers` to your config:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
import { MyProvider } from './MyProvider'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
providers: [MyProvider], // highlight-line
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Then build your Custom Provider as follows:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import React, { createContext, useContext } from 'react'
|
||||
|
||||
const MyCustomContext = React.createContext(myCustomValue)
|
||||
|
||||
export const MyProvider: React.FC = ({ children }) => {
|
||||
return (
|
||||
<MyCustomContext.Provider value={myCustomValue}>
|
||||
{children}
|
||||
</MyCustomContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useMyCustomContext = () => useContext(MyCustomContext)
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Reminder:</strong> Custom Providers are by definition Client Components. This means they must include the `use client` directive at the top of their files and cannot use server-only code.
|
||||
</Banner>
|
||||
|
||||
## Custom Collection Components
|
||||
|
||||
Collection Components are those that effect [Collection](../configuration/collections)-specific UI within the [Admin Panel](./overview), such as the save button. You can override Collection Components through the `admin.components` property on any [Collection Config](../configuration/collections).
|
||||
|
||||
Here is an example showing what it might look like to swap out Collection Components for your own. See [Building Custom Components](#building-custom-components) for exact details on how to build them:
|
||||
|
||||
```ts
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
import { CustomSaveButton } from './CustomSaveButton'
|
||||
|
||||
export const MyCollection: SanitizedCollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
admin: {
|
||||
components: {
|
||||
edit: {
|
||||
SaveButton: CustomSaveButton, // highlight-line
|
||||
},
|
||||
},
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -87,165 +144,199 @@ You can override components on a collection-by-collection basis via the `admin.c
|
||||
| **`BeforeListTable`** | Array of components to inject _before_ the built-in List view's table |
|
||||
| **`AfterList`** | Array of components to inject _after_ the built-in List view |
|
||||
| **`AfterListTable`** | Array of components to inject _after_ the built-in List view's table |
|
||||
| **`edit.SaveButton`** | Replace the default `Save` button with a custom component. Drafts must be disabled |
|
||||
| **`edit.SaveDraftButton`** | Replace the default `Save Draft` button with a custom component. Drafts must be enabled and autosave must be disabled. |
|
||||
| **`edit.PublishButton`** | Replace the default `Publish` button with a custom component. Drafts must be enabled. |
|
||||
| **`edit.PreviewButton`** | Replace the default `Preview` button with a custom component. |
|
||||
| **`views`** | Override or create new [Views](./views) within the [Admin Panel](./overview). |
|
||||
| **`edit.SaveButton`** | Replace the default `Save` button with a Custom Component. Drafts must be disabled |
|
||||
| **`edit.SaveDraftButton`** | Replace the default `Save Draft` button with a Custom Component. Drafts must be enabled and autosave must be disabled. |
|
||||
| **`edit.PublishButton`** | Replace the default `Publish` button with a Custom Component. Drafts must be enabled. |
|
||||
| **`edit.PreviewButton`** | Replace the default `Preview` button with a Custom Component. |
|
||||
| **`views`** | Override or create new views within the [Admin Panel](./overview). [More details](./views). |
|
||||
|
||||
Here is a full example showing how to swap some of these components for your own:
|
||||
## Custom Global Components
|
||||
|
||||
`CustomSaveButton.tsx`
|
||||
Global Components are those that effect [Global](../configuration/globals)-specific UI within the [Admin Panel](./overview), such as the save button. You can override Global Components through the `admin.components` property on any [Global Config](../configuration/globals).
|
||||
|
||||
```tsx
|
||||
import { CustomSaveButtonProps } from 'payload/types'
|
||||
|
||||
const CustomSaveButton: CustomSaveButtonProps = ({
|
||||
DefaultButton,
|
||||
label,
|
||||
save
|
||||
}) => {
|
||||
return (
|
||||
<DefaultButton
|
||||
label={label}
|
||||
save={save}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`CustomSaveDraftButton.tsx`
|
||||
|
||||
```tsx
|
||||
import { CustomSaveDraftButtonProps } from 'payload/types'
|
||||
|
||||
export const CustomSaveDraftButton: CustomSaveDraftButtonProps = ({
|
||||
DefaultButton,
|
||||
disabled,
|
||||
label,
|
||||
saveDraft,
|
||||
}) => {
|
||||
return (
|
||||
<DefaultButton
|
||||
label={label}
|
||||
disabled={disabled}
|
||||
saveDraft={saveDraft}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`CustomPublishButton.tsx`
|
||||
|
||||
```tsx
|
||||
import { CustomPreviewButtonProps } from 'payload/types'
|
||||
|
||||
export const CustomPublishButton: CustomPublishButtonProps = ({
|
||||
DefaultButton,
|
||||
disabled,
|
||||
label,
|
||||
publish,
|
||||
}) => {
|
||||
return (
|
||||
<DefaultButton
|
||||
label={label}
|
||||
disabled={disabled}
|
||||
publish={publish}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`CustomPreviewButton`
|
||||
|
||||
```tsx
|
||||
import { CustomPreviewButtonProps } from 'payload/types'
|
||||
|
||||
export const CustomPreviewButton: CustomPreviewButtonProps = ({
|
||||
DefaultButton,
|
||||
disabled,
|
||||
label,
|
||||
preview,
|
||||
}) => {
|
||||
return (
|
||||
<DefaultButton
|
||||
label={label}
|
||||
disabled={disabled}
|
||||
preview={preview}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`collection.ts`
|
||||
|
||||
```tsx
|
||||
import * as React from 'react'
|
||||
Here is an example showing what it might look like to swap out Global Components for your own. See [Building Custom Components](#building-custom-components) for exact details on how to build them:
|
||||
|
||||
```ts
|
||||
import type { SanitizedGlobalConfig } from 'payload/types'
|
||||
import { CustomSaveButton } from './CustomSaveButton'
|
||||
import { CustomSaveDraftButton } from './CustomSaveDraftButton'
|
||||
import { CustomPublishButton } from './CustomPublishButton'
|
||||
import { CustomPreviewButton } from './CustomPreviewButton'
|
||||
|
||||
export const MyCollection: SanitizedCollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
export const MyGlobal: SanitizedGlobalConfig = {
|
||||
slug: 'my-global',
|
||||
admin: {
|
||||
components: {
|
||||
edit: {
|
||||
SaveButton: CustomSaveButton,
|
||||
SaveDraftButton: CustomSaveDraftButton,
|
||||
PublishButton: CustomPublishButton,
|
||||
PreviewButton: CustomPreviewButton,
|
||||
elements: {
|
||||
SaveButton: CustomSaveButton, // highlight-line
|
||||
},
|
||||
},
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Globals
|
||||
|
||||
As with Collections, you can override components on a global-by-global basis via the `admin.components` property.
|
||||
The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`elements.SaveButton`** | Replace the default `Save` button with a custom component. Drafts must be disabled |
|
||||
| **`elements.SaveDraftButton`** | Replace the default `Save Draft` button with a custom component. Drafts must be enabled and autosave must be disabled. |
|
||||
| **`elements.PublishButton`** | Replace the default `Publish` button with a custom component. Drafts must be enabled. |
|
||||
| **`elements.PreviewButton`** | Replace the default `Preview` button with a custom component. |
|
||||
| **`views`** | Override or create new [Views](./views) within the [Admin Panel](./overview). |
|
||||
| **`elements.SaveButton`** | Replace the default `Save` button with a Custom Component. Drafts must be disabled. |
|
||||
| **`elements.SaveDraftButton`** | Replace the default `Save Draft` button with a Custom Component. Drafts must be enabled and autosave must be disabled. |
|
||||
| **`elements.PublishButton`** | Replace the default `Publish` button with a Custom Component. Drafts must be enabled. |
|
||||
| **`elements.PreviewButton`** | Replace the default `Preview` button with a Custom Component. |
|
||||
| **`views`** | Override or create new views within the [Admin Panel](./overview). [More details](./views). |
|
||||
|
||||
## Custom providers
|
||||
## Building Custom Components
|
||||
|
||||
As your admin customizations gets more complex you may want to share state between fields or other components. You can add custom providers to do add your own context to any Payload app for use in other custom components within the [Admin Panel](./overview). Within your config add `admin.components.providers`, these can be used to share context or provide other custom functionality. See the [React Context](https://reactjs.org/docs/context.html) docs to learn more.
|
||||
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, with the exception of [Custom Providers](#custom-providers). This enables the use of the [Local API](../local-api) directly in the front-end.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Reminder:</strong> Don't forget to pass the **children** prop through the provider
|
||||
component for the admin UI to show
|
||||
</Banner>
|
||||
To make building Custom Components as easy as possible, Payload automatically provides common props, such as the [`payload`](../local-api/overview) class and the [`i18n`](../configuration/i18n) object. This means that when building Custom Components within the Admin Panel, you do not have to get these yourself like you would from an external application.
|
||||
|
||||
## Styling Custom Components
|
||||
|
||||
Payload exports its SCSS variables and mixins for reuse in your own custom components. This is helpful in cases where you might want to style a custom input similarly to Payload's built-ini styling, so it blends more thoroughly into the existing admin UI.
|
||||
|
||||
To make use of Payload SCSS variables / mixins to use directly in your own components, you can import them as follows:
|
||||
|
||||
```
|
||||
@import '~payload/scss';
|
||||
```
|
||||
|
||||
## Getting the current language
|
||||
|
||||
When developing custom components you can support multiple languages to be consistent with Payload's i18n support. The best way to do this is to add your translation resources to the [i18n configuration](https://payloadcms.com/docs/configuration/i18n) and import `useTranslation` from `@payloadcms/ui/providers/Translation` in your components.
|
||||
|
||||
For example:
|
||||
Here is an example:
|
||||
|
||||
```tsx
|
||||
import { useTranslation } from '@payloadcms/ui/providers/Translation'
|
||||
import React from 'react'
|
||||
|
||||
const CustomComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
const { t, i18n } = useTranslation()
|
||||
// highlight-end
|
||||
const MyServerComponent = async ({
|
||||
payload // highlight-line
|
||||
}) => {
|
||||
const page = await payload.findByID({
|
||||
collection: 'pages',
|
||||
id: '123',
|
||||
})
|
||||
|
||||
return (
|
||||
<p>{page.title}</p>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Each Custom Component receives the following props by default:
|
||||
|
||||
| Prop | Description |
|
||||
| ------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `payload` | The [Payload](../local-api/overview) class. |
|
||||
| `i18n` | The [i18n](../i18n) object. |
|
||||
|
||||
Custom Components also receive various other props that are specific to the context in which the Custom Component is being rendered. For example, [Custom Views](./views) receive the `user` prop. For a full list of available props, consult the documentation related to the specific component you are working with.
|
||||
|
||||
<Banner type="success">
|
||||
See [Root Components](#custom-root-components), [Collection Components](#custom-collection-components), [Global Components](#custom-global-components), or [Field Components](#custom-field-components) for a complete list of all available components.
|
||||
</Banner>
|
||||
|
||||
### Client Components
|
||||
|
||||
When [Building Custom Components](#building-custom-components), it's still possible to use client-side code such as `useState` or the `window` object. To do this, simply add the `use client` directive at the top of your file. Payload will automatically detect and remove all default [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types) before rendering your component.
|
||||
|
||||
```tsx
|
||||
'use client' // highlight-line
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export const MyClientComponent: React.FC = () => {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
Clicked {count} times
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Reminder:</strong>
|
||||
Client Components cannot be passed [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types). If you are rendering your Client Component _from within_ a Server Component, ensure that its props are serializable.
|
||||
</Banner>
|
||||
|
||||
### Accessing the Payload Config
|
||||
|
||||
From any Server Component, the [Payload Config](../configuration/overview) can be retrieved using the `payload` prop:
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
|
||||
export default async function MyServerComponent({
|
||||
payload: {
|
||||
config // highlight-line
|
||||
}
|
||||
}) {
|
||||
return (
|
||||
<Link href={config.serverURL}>
|
||||
Go Home
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
But the Payload Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) by design. It is full of custom validation functions, React components, etc. This means that the Payload Config, in its entirety, cannot be passed directly to Client Components.
|
||||
|
||||
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](./hooks#useconfig) hook:
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import { useConfig } from '@payloadcms/ui'
|
||||
|
||||
export const MyClientComponent: React.FC = () => {
|
||||
const { serverURL } = useConfig() // highlight-line
|
||||
|
||||
return (
|
||||
<Link href={serverURL}>
|
||||
Go Home
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See [Using Hooks](#using-hooks) for more details.
|
||||
</Banner>
|
||||
|
||||
### Using Hooks
|
||||
|
||||
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](./hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useDocumentInfo } from '@payloadcms/ui'
|
||||
|
||||
export const MyClientComponent: React.FC = () => {
|
||||
const { slug } = useDocumentInfo() // highlight-line
|
||||
|
||||
return (
|
||||
<p>{`Entity slug: ${slug}`}</p>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See the [Hooks](./hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
### Getting the Current Language
|
||||
|
||||
All Custom Components can support multiple languages to be consistent with Payload's [Internationalization](../configuration/i18n). To do this, first add your translation resources to the [I18n Config](../configuration/i18n).
|
||||
|
||||
From any Server Component, you can translate resources using the `getTranslation` function from `@payloadcms/translations`. All Server Components automatically receive the `i18n` object as a prop by default.
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
export default async function MyServerComponent({ i18n }) {
|
||||
const translatedTitle = getTranslation(myTranslation, i18n) // highlight-line
|
||||
|
||||
return (
|
||||
<p>{translatedTitle}</p>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The best way to do this within a Client Component is to import the `useTranslation` hook from `@payloadcms/ui`:
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
|
||||
export const MyClientComponent: React.FC = () => {
|
||||
const { t, i18n } = useTranslation() // highlight-line
|
||||
|
||||
return (
|
||||
<ul>
|
||||
@@ -257,23 +348,94 @@ const CustomComponent: React.FC = () => {
|
||||
}
|
||||
```
|
||||
|
||||
## Getting the current locale
|
||||
<Banner type="success">
|
||||
See the [Hooks](./hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
In any custom component you can get the selected locale with `useLocale` hook. `useLocale` returns the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example:
|
||||
### Getting the Current Locale
|
||||
|
||||
All [Custom Views](./views) can support multiple locales to be consistent with Payload's [Localization](../configuration/localization). They automatically receive the `locale` object as a prop by default. This can be used to scope API requests, etc.:
|
||||
|
||||
```tsx
|
||||
import { useLocale } from 'payload/components/utilities'
|
||||
import React from 'react'
|
||||
|
||||
export default async function MyServerComponent({ payload, locale }) {
|
||||
const localizedPage = await payload.findByID({
|
||||
collection: 'pages',
|
||||
id: '123',
|
||||
locale,
|
||||
})
|
||||
|
||||
return (
|
||||
<p>{localizedPage.title}</p>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The best way to do this within a Client Component is to import the `useLocale` hook from `@payloadcms/ui`:
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import { useLocale } from '@payloadcms/ui'
|
||||
|
||||
const Greeting: React.FC = () => {
|
||||
// highlight-start
|
||||
const locale = useLocale()
|
||||
// highlight-end
|
||||
const locale = useLocale() // highlight-line
|
||||
|
||||
const trans = {
|
||||
en: 'Hello',
|
||||
es: 'Hola',
|
||||
}
|
||||
|
||||
return <span> {trans[locale.code]} </span>
|
||||
return (
|
||||
<span>{trans[locale.code]}</span>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
See the [Hooks](./hooks) documentation for a full list of available hooks.
|
||||
</Banner>
|
||||
|
||||
### Styling Custom Components
|
||||
|
||||
Payload has a robust [CSS Library](./customizing-css) that you can style your Custom Components similarly to Payload's built-in styling. This will ensure that your Custom Component matches the existing design system, and so that it automatically adapts to any theme changes.
|
||||
|
||||
To apply custom styles, simply import your own `.css` or `.scss` file into your Custom Component:
|
||||
|
||||
```tsx
|
||||
import './index.scss'
|
||||
|
||||
export const MyComponent: React.FC = () => {
|
||||
return (
|
||||
<div className="my-component">
|
||||
My Custom Component
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Then to colorize your Custom Component's background, for example, you can use the following CSS:
|
||||
|
||||
```scss
|
||||
.my-component {
|
||||
background-color: var(--theme-elevation-500);
|
||||
}
|
||||
```
|
||||
|
||||
Payload also exports its [SCSS](https://sass-lang.com) library for reuse which includes mixins, etc. To use this, simply import it as follows into your `.scss` file:
|
||||
|
||||
```scss
|
||||
@import '~payload/scss';
|
||||
|
||||
.my-component {
|
||||
@include mid-break {
|
||||
background-color: var(--theme-elevation-900);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong>
|
||||
You can also drill into Payload's own component styles, or easily apply global, app-wide CSS. More on that [here](./customizing-css).
|
||||
</Banner>
|
||||
|
||||
@@ -2,50 +2,64 @@
|
||||
title: Customizing CSS & SCSS
|
||||
label: Customizing CSS
|
||||
order: 60
|
||||
desc: Customize your Payload admin panel further by adding your own CSS or SCSS style sheet to the configuration, powerful theme and design options are waiting for you.
|
||||
desc: Customize the Payload Admin Panel further by adding your own CSS or SCSS style sheet to the configuration, powerful theme and design options are waiting for you.
|
||||
keywords: admin, css, scss, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
## Adding your own CSS / SCSS
|
||||
Customizing the Payload [Admin Panel](./overview) through CSS alone is one of the easiest and most powerful ways to customize the look and feel of the dashboard. To allow for this level of customization, Payload:
|
||||
|
||||
You can add your own CSS by providing your base Payload config with a path to your own CSS or SCSS. Customize the styling of any part of the Payload dashboard as necessary.
|
||||
1. Exposes a [root-level stylesheet](#global-css) for you to easily to inject custom selectors
|
||||
1. Provides a [CSS library](#css-library) that can be easily overridden or extended
|
||||
1. Uses [BEM naming conventions](http://getbem.com) so that class names are globally accessible
|
||||
|
||||
To do so, provide your base Payload config with a path to your own stylesheet. It can be either a CSS or SCSS file.
|
||||
To customize the CSS within the Admin UI, determine scope and change you'd like to make, and then add your own CSS or SCSS to the configuration as needed.
|
||||
|
||||
**Example in payload.config.js:**
|
||||
## Global CSS
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import path from 'path'
|
||||
Global CSS refers to the CSS that is applied to the entire [Admin Panel](./overview). This is where you can have a significant impact to the look and feel of the Admin UI through just a few lines of code.
|
||||
|
||||
const config = buildConfig({
|
||||
admin: {
|
||||
css: path.resolve(__dirname, 'relative/path/to/stylesheet.scss'),
|
||||
},
|
||||
})
|
||||
You can add your own global CSS through the root `custom.scss` file of your app. This file is loaded into the root of the Admin Panel and can be used to inject custom selectors or styles however needed.
|
||||
|
||||
Here is an example of how you might target the Dashboard View and change the background color:
|
||||
|
||||
```scss
|
||||
.dashboard {
|
||||
background-color: red; // highlight-line
|
||||
}
|
||||
```
|
||||
|
||||
## Overriding built-in styles
|
||||
|
||||
To make it as easy as possible for you to override our styles, Payload uses [BEM naming conventions](http://getbem.com/) for all CSS within the Admin UI. If you provide your own CSS, you can override any built-in styles easily.
|
||||
|
||||
In addition to adding your own style definitions, you can also override Payload's built-in CSS variables. We use as much as possible behind the scenes, and you can override any of them that you'd like to.
|
||||
|
||||
You can find the built-in Payload CSS variables within [`./src/admin/scss/app.scss`](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/scss/app.scss) and [`./src/admin/scss/colors.scss`](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/scss/colors.scss). The following variables are defined and can be overridden:
|
||||
|
||||
- Breakpoints
|
||||
- Base color shades (white to black by default)
|
||||
- Success / warning / error color shades
|
||||
- Theme-specific colors (background, input background, text color, etc.)
|
||||
- Elevation colors (used to determine how "bright" something should be when compared to the background)
|
||||
- Fonts
|
||||
- Horizontal gutter
|
||||
|
||||
### Dark mode
|
||||
|
||||
<Banner type="warning">
|
||||
If you're overriding colors or theme elevations, make sure to consider how your changes will
|
||||
affect dark mode.
|
||||
<strong>Note:</strong>
|
||||
If you are building [Custom Components](./overview), it is best to import your own stylesheets directly into your components, rather than using the global stylesheet. You can continue to use the [CSS library](#css-library) as needed.
|
||||
</Banner>
|
||||
|
||||
By default, Payload automatically overrides all `--theme-elevation`s and inverts all success / warning / error shades to suit dark mode. We also update some base theme variables like `--theme-bg`, `--theme-text`, etc.
|
||||
## CSS Library
|
||||
|
||||
To make it as easy as possible for you to override default styles, Payload uses [BEM naming conventions](http://getbem.com/) for all CSS within the Admin UI. If you provide your own CSS, you can override any built-in styles easily, including targeting nested components and their various component states.
|
||||
|
||||
You can also override Payload's built-in [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties). These variables are widely consumed by the Admin Panel, so modifying them has a significant impact on the look and feel of the Admin UI.
|
||||
|
||||
The following variables are defined and can be overridden:
|
||||
|
||||
- [Breakpoints](https://github.com/payloadcms/payload/blob/beta/packages/ui/src/scss/queries.scss)
|
||||
- [Colors](https://github.com/payloadcms/payload/blob/beta/packages/ui/src/scss/colors.scss)
|
||||
- Base color shades (white to black by default)
|
||||
- Success / warning / error color shades
|
||||
- Theme-specific colors (background, input background, text color, etc.)
|
||||
- Elevation colors (used to determine how "bright" something should be when compared to the background)
|
||||
- [Sizing](https://github.com/payloadcms/payload/blob/beta/packages/ui/src/scss/app.scss)
|
||||
- Horizontal gutter
|
||||
- Transition speeds
|
||||
- Font sizes
|
||||
- Etc.
|
||||
|
||||
For an up-to-date, comprehensive list of all available variables, please refer to the [Source Code](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss).
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Warning:</strong>
|
||||
If you're overriding colors or theme elevations, make sure to consider how [your changes will affect dark mode](#dark-mode).
|
||||
</Banner>
|
||||
|
||||
#### Dark Mode
|
||||
|
||||
Colors are designed to automatically adapt to theme of the [Admin Panel](./overview). By default, Payload automatically overrides all `--theme-elevation` colors and inverts all success / warning / error shades to suit dark mode. We also update some base theme variables like `--theme-bg`, `--theme-text`, etc.
|
||||
|
||||
@@ -1,27 +1,78 @@
|
||||
---
|
||||
title: Environment Variables in Admin UI
|
||||
title: Environment Variables in the Admin Panel
|
||||
label: Environment Variables
|
||||
order: 100
|
||||
desc: NEEDS TO BE WRITTEN
|
||||
---
|
||||
|
||||
## Admin environment vars
|
||||
Environment variables are a way to store sensitive information that your application needs to function. This could be anything from API keys to database credentials. Payload allows you to provide environment variables to your [Admin Panel](./overview) that can be accessed by your [Custom Components](./components) and [Custom Endpoints](../rest-api/overview#custom-endpoints).
|
||||
|
||||
For security and safety reasons, the Admin Panel not **not** include environment variables in client-side bundle by default. But, Payload provides a mechanism to expose environment variables to the client-side bundle when needed.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
Be careful about what variables you provide to your client-side code. Analyze every single one to
|
||||
make sure that you're not accidentally leaking anything that an attacker could exploit. Only keys
|
||||
that are safe for anyone to read in plain text should be provided to your Admin panel.
|
||||
make sure that you're not accidentally leaking anything that an outside attacker could exploit. Only keys
|
||||
that are safe for anyone to read in plain text should be provided to your [Admin Panel](./overview).
|
||||
</Banner>
|
||||
|
||||
By default, `env` variables are **not** provided to the Admin panel for security and safety reasons.
|
||||
But, Payload provides you with a way to still provide `env` vars to your frontend code.
|
||||
## Client-side Environments
|
||||
|
||||
**Payload will automatically supply any present `env` variables that are prefixed with `PAYLOAD_PUBLIC_` directly to the Admin panel.**
|
||||
If you are building [Custom Components](./components) that are using Client Components and need to access environment variables from the client-side, you can do so by prefixing your environment variables with `NEXT_PUBLIC_`.
|
||||
|
||||
For example, if you've got the following environment variable:
|
||||
|
||||
`PAYLOAD_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_XXXXXXXXXXXXXXXXXX`
|
||||
```bash
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_XXXXXXXXXXXXXXXXXX
|
||||
```
|
||||
|
||||
This key will automatically be made available to the Payload bundle and can be referenced in your Admin component code as `process.env.PAYLOAD_PUBLIC_STRIPE_PUBLISHABLE_KEY`.
|
||||
This key will automatically be made available to the client-side Payload bundle and can be referenced in your Custom Component as follows:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
const stripeKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY // highlight-line
|
||||
|
||||
const MyClientComponent = () => {
|
||||
// do something with the key
|
||||
|
||||
return (
|
||||
<div>
|
||||
My Client Component
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
<strong>Note:</strong>
|
||||
For more information on how to use environment variables in the [Admin Panel](./overview), see the [Next.js documentation](https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables).
|
||||
</Banner>
|
||||
|
||||
## Server-side Environments
|
||||
|
||||
All [Custom Endpoints](../rest-api/overview#custom-endpoints) and [Custom Components](./components) that are Server Components are unaffected by this restriction and can access any environment variables you provide. For example, if you've got the following environment variable:
|
||||
|
||||
```bash
|
||||
STRIPE_SECRET=sk_test_XXXXXXXXXXXXXXXXXX
|
||||
```
|
||||
|
||||
This key will be available to your Server Components as follows:
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
|
||||
const stripeSecret = process.env.STRIPE_SECRET // highlight-line
|
||||
|
||||
const MyServerComponent = async () => {
|
||||
// do something with the secret
|
||||
|
||||
return (
|
||||
<div>
|
||||
My Server Component
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -21,17 +21,17 @@ All Payload fields support the ability to swap in your own React components. So,
|
||||
| Component | Description |
|
||||
| ------------ | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. |
|
||||
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. [More](#cell-component) |
|
||||
| **`Field`** | Swap out the field itself within all `Edit` views. [More](#field-component) |
|
||||
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. [More details](#cell-component).|
|
||||
| **`Field`** | Swap out the field itself within all `Edit` views. [More details](#field-component). |
|
||||
|
||||
As an alternative to replacing the entire Field component, you may want to keep the majority of the default Field component and only swap components within. This allows you to replace the **`Label`** or **`Error`** within a field component or add additional components inside the field with **`beforeInput`** or **`afterInput`**. **`beforeInput`** and **`afterInput`** are allowed in any fields that don't contain other fields, except [UI](/docs/fields/ui) and [Rich Text](/docs/fields/rich-text).
|
||||
|
||||
| Component | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Label`** | Override the default Label in the Field Component. [More](#label-component) |
|
||||
| **`Error`** | Override the default Label in the Field Component. [More](#error-component) |
|
||||
| **`beforeInput`** | An array of elements that will be added before `input`/`textarea` elements. [More](#afterinput-and-beforeinput) |
|
||||
| **`afterInput`** | An array of elements that will be added after `input`/`textarea` elements. [More](#afterinput-and-beforeinput) |
|
||||
| **`Label`** | Override the default Label in the Field Component. [More details](#label-component). |
|
||||
| **`Error`** | Override the default Label in the Field Component. [More details](#error-component). |
|
||||
| **`beforeInput`** | An array of elements that will be added before `input`/`textarea` elements. [More details](#afterinput-and-beforeinput).|
|
||||
| **`afterInput`** | An array of elements that will be added after `input`/`textarea` elements. [More details](#afterinput-and-beforeinput). |
|
||||
|
||||
## Cell Component
|
||||
|
||||
@@ -41,7 +41,7 @@ These are the props that will be passed to your custom Cell to use in your own c
|
||||
| ---------------- | ----------------------------------------------------------------- |
|
||||
| **`field`** | An object that includes the field configuration. |
|
||||
| **`colIndex`** | A unique number for the column in the list. |
|
||||
| **`collection`** | An object with the config of the collection that the field is in. |
|
||||
| **`collection`** | An object with the config of the Collection that the field is in. |
|
||||
| **`cellData`** | The data for the field that the cell represents. |
|
||||
| **`rowData`** | An object with all the field values for the row. |
|
||||
|
||||
@@ -49,7 +49,7 @@ These are the props that will be passed to your custom Cell to use in your own c
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import type { Props } from 'payload/components/views/Cell'
|
||||
import type { CellProps } from 'payload'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'custom-cell'
|
||||
@@ -70,11 +70,12 @@ When writing your own custom components you can make use of a number of hooks to
|
||||
When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows:
|
||||
|
||||
```tsx
|
||||
import { useField } from 'payload/components/forms'
|
||||
'use client'
|
||||
import { useField } from '@payloadcms/ui'
|
||||
|
||||
const CustomTextField: React.FC<{ path: string }> = ({ path }) => {
|
||||
// highlight-start
|
||||
const { value, setValue } = useField<string>({ path })
|
||||
const { value, setValue } = useField({ path })
|
||||
// highlight-end
|
||||
|
||||
return <input onChange={(e) => setValue(e.target.value)} value={value} />
|
||||
@@ -99,10 +100,10 @@ These are the props that will be passed to your custom Label.
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { getTranslation } from 'payload/utilities/getTranslation'
|
||||
import { useTranslation } from '@payloadcms/ui'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
type Props = {
|
||||
htmlFor?: string
|
||||
@@ -164,7 +165,7 @@ With these properties you can add multiple components before and after the input
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import { Field } from 'payload/types'
|
||||
import type { Field } from 'payload/types'
|
||||
|
||||
import './style.scss'
|
||||
|
||||
|
||||
@@ -6,7 +6,12 @@ desc: Make use of all of the powerful React hooks that Payload provides.
|
||||
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Payload provides a variety of powerful hooks that can be used within your own React components. With them, you can interface with Payload itself and build just about any type of complex customization you can think of—directly in familiar React code.
|
||||
Payload provides a variety of powerful [React Hooks](https://react.dev/reference/react-dom/hooks) that can be used within your own [Custom Components](./components), such as [Custom Fields](./fields). With them, you can interface with Payload itself and build just about any type of complex customization you can think of.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Reminder:</strong>
|
||||
All Custom Components are [React Server Components](https://react.dev/reference/rsc/server-components) by default. Hooks, on the other hand, are only available in client-side environments. To use hooks, [ensure your component is a client component](./components#client-components).
|
||||
</Banner>
|
||||
|
||||
## useField
|
||||
|
||||
@@ -15,41 +20,53 @@ The `useField` hook is used internally within every applicable Payload field com
|
||||
Outside of internal use, its most common use-case is in custom `Field` components. When you build a custom React `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows:
|
||||
|
||||
```tsx
|
||||
import { useField } from 'payload/components/forms'
|
||||
'use client'
|
||||
import { useField } from '@payloadcms/ui'
|
||||
|
||||
type Props = { path: string }
|
||||
|
||||
const CustomTextField: React.FC<Props> = ({ path }) => {
|
||||
const CustomTextField: React.FC = ({ name }) => {
|
||||
// highlight-start
|
||||
const { value, setValue } = useField<string>({ path })
|
||||
const { value, setValue, path } = useField({ path: name })
|
||||
// highlight-end
|
||||
|
||||
return <input onChange={(e) => setValue(e.target.value)} value={value.path} />
|
||||
return (
|
||||
<input
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
value={value.path}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `useField` hook accepts an `args` object and sends back information and helpers for you to make use of:
|
||||
The `useField` hook accepts the following arguments:
|
||||
|
||||
| Property | Description |
|
||||
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `disableFormData` | If `true`, the field will not be included in the form data when the form is submitted. |
|
||||
| `hasRows` | |
|
||||
| `path` | If you do not provide a `path` or a `name`, this hook will look for one using the `useFieldPath` hook. |
|
||||
| `validate` | If you do not provide a `validate` function, the field will be validated _on the client_ before submitting to the server. |
|
||||
|
||||
Here is what `useField` hook returns:
|
||||
|
||||
```ts
|
||||
const field = useField<string>({
|
||||
path: 'fieldPathHere', // required
|
||||
validate: myValidateFunc, // optional
|
||||
disableFormData?: false, // if true, the field's data will be ignored
|
||||
condition?: myConditionHere, // optional, used to skip validation if condition fails
|
||||
})
|
||||
|
||||
// Here is what `useField` sends back
|
||||
const {
|
||||
showError, // whether or not the field should show as errored
|
||||
errorMessage, // the error message to show, if showError
|
||||
value, // the current value of the field from the form
|
||||
formSubmitted, // if the form has been submitted
|
||||
formProcessing, // if the form is currently processing
|
||||
setValue, // method to set the field's value in form state
|
||||
initialValue, // the initial value that the field mounted with
|
||||
} = field;
|
||||
|
||||
// The rest of your component goes here
|
||||
type FieldResult<T> = {
|
||||
errorMessage?: string
|
||||
errorPaths?: string[]
|
||||
filterOptions?: FilterOptionsResult
|
||||
formInitializing: boolean
|
||||
formProcessing: boolean
|
||||
formSubmitted: boolean
|
||||
initialValue?: T
|
||||
path: string
|
||||
permissions: FieldPermissions
|
||||
readOnly?: boolean
|
||||
rows?: Row[]
|
||||
schemaPath: string
|
||||
setValue: (val: unknown, disableModifyingForm?: boolean) => voi
|
||||
showError: boolean
|
||||
valid?: boolean
|
||||
value: T
|
||||
}
|
||||
```
|
||||
|
||||
## useFormFields
|
||||
@@ -66,7 +83,8 @@ Thanks to the awesome package [`use-context-selector`](https://github.com/dai-sh
|
||||
You can pass a Redux-like selector into the hook, which will ensure that you retrieve only the field that you want. The selector takes an argument with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
|
||||
|
||||
```tsx
|
||||
import { useFormFields } from 'payload/components/forms'
|
||||
'use client'
|
||||
import type { useFormFields } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// Get only the `amount` field state, and only cause a rerender when that field changes
|
||||
@@ -88,7 +106,8 @@ const MyComponent: React.FC = () => {
|
||||
You can do lots of powerful stuff by retrieving the full form state, like using built-in helper functions to reduce field state to values only, or to retrieve sibling data by path.
|
||||
|
||||
```tsx
|
||||
import { useAllFormFields, reduceFieldsToValues, getSiblingData } from 'payload/components/forms';
|
||||
'use client'
|
||||
import { useAllFormFields, reduceFieldsToValues, getSiblingData } from '@payloadcms/ui'
|
||||
|
||||
const ExampleComponent: React.FC = () => {
|
||||
// the `fields` const will be equal to all fields' state,
|
||||
@@ -111,7 +130,7 @@ const ExampleComponent: React.FC = () => {
|
||||
|
||||
#### Updating other fields' values
|
||||
|
||||
If you are building a custom component, then you should use `setValue` which is returned from the `useField` hook to programmatically set your field's value. But if you're looking to update _another_ field's value, you can use `dispatchFields` returned from `useFormFields`.
|
||||
If you are building a Custom Component, then you should use `setValue` which is returned from the `useField` hook to programmatically set your field's value. But if you're looking to update _another_ field's value, you can use `dispatchFields` returned from `useFormFields`.
|
||||
|
||||
You can send the following actions to the `dispatchFields` function.
|
||||
|
||||
@@ -141,7 +160,7 @@ The `useForm` hook can be used to interact with the form itself, and sends back
|
||||
up-to-date. They will be removed from this hook's response in an upcoming version.
|
||||
</Banner>
|
||||
|
||||
The `useForm` hook returns an object with the following properties: |
|
||||
The `useForm` hook returns an object with the following properties:
|
||||
|
||||
<TableWithDrawers
|
||||
columns={[
|
||||
@@ -394,7 +413,7 @@ export const CustomArrayManager = () => {
|
||||
}`}
|
||||
</pre>
|
||||
|
||||
<p>An example config to go along with the custom component</p>
|
||||
<p>An example config to go along with the Custom Component</p>
|
||||
<pre>
|
||||
{`const ExampleCollection = {
|
||||
slug: "example-collection",
|
||||
@@ -491,7 +510,7 @@ export const CustomArrayManager = () => {
|
||||
}`}
|
||||
</pre>
|
||||
|
||||
<p>An example config to go along with the custom component</p>
|
||||
<p>An example config to go along with the Custom Component</p>
|
||||
<pre>
|
||||
{`const ExampleCollection = {
|
||||
slug: "example-collection",
|
||||
@@ -601,7 +620,7 @@ export const CustomArrayManager = () => {
|
||||
}`}
|
||||
</pre>
|
||||
|
||||
<p>An example config to go along with the custom component</p>
|
||||
<p>An example config to go along with the Custom Component</p>
|
||||
<pre>
|
||||
{`const ExampleCollection = {
|
||||
slug: "example-collection",
|
||||
@@ -639,19 +658,20 @@ export const CustomArrayManager = () => {
|
||||
|
||||
The `useCollapsible` hook allows you to control parent collapsibles:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------ | --- |
|
||||
| **`isCollapsed`** | State of the collapsible. `true` if open, `false` if collapsed |
|
||||
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise |
|
||||
| **`toggle`** | Toggles the state of the nearest collapsible |
|
||||
| **`isWithinCollapsible`** | Determine when you are within another collaspible | |
|
||||
| Property | Description |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| **`isCollapsed`** | State of the collapsible. `true` if open, `false` if collapsed. |
|
||||
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise. |
|
||||
| **`toggle`** | Toggles the state of the nearest collapsible. |
|
||||
| **`isWithinCollapsible`** | Determine when you are within another collapsible. |
|
||||
|
||||
**Example:**
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
import { useCollapsible } from 'payload/components/utilities'
|
||||
import { useCollapsible } from '@payloadcms/ui'
|
||||
|
||||
const CustomComponent: React.FC = () => {
|
||||
const { isCollapsed, toggle } = useCollapsible()
|
||||
@@ -673,8 +693,8 @@ The `useDocumentInfo` hook provides lots of information about the document curre
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`collection`** | If the doc is a collection, its collection config will be returned |
|
||||
| **`global`** | If the doc is a global, its global config will be returned |
|
||||
| **`collection`** | If the doc is a collection, its Collection Config will be returned |
|
||||
| **`global`** | If the doc is a global, its Global Config will be returned |
|
||||
| **`id`** | If the doc is a collection, its ID will be returned |
|
||||
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences |
|
||||
| **`versions`** | Versions of the current doc |
|
||||
@@ -687,7 +707,8 @@ The `useDocumentInfo` hook provides lots of information about the document curre
|
||||
**Example:**
|
||||
|
||||
```tsx
|
||||
import { useDocumentInfo } from 'payload/components/utilities'
|
||||
'use client'
|
||||
import { useDocumentInfo } from '@payloadcms/ui'
|
||||
|
||||
const LinkFromCategoryToPosts: React.FC = () => {
|
||||
// highlight-start
|
||||
@@ -709,10 +730,11 @@ const LinkFromCategoryToPosts: React.FC = () => {
|
||||
|
||||
## useLocale
|
||||
|
||||
In any custom component you can get the selected locale object with the `useLocale` hook. `useLocale`gives you the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example:
|
||||
In any Custom Component you can get the selected locale object with the `useLocale` hook. `useLocale`gives you the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example:
|
||||
|
||||
```tsx
|
||||
import { useLocale } from 'payload/components/utilities'
|
||||
'use client'
|
||||
import { useLocale } from '@payloadcms/ui'
|
||||
|
||||
const Greeting: React.FC = () => {
|
||||
// highlight-start
|
||||
@@ -743,8 +765,9 @@ Useful to retrieve info about the currently logged in user as well as methods fo
|
||||
| **`permissions`** | The permissions of the current user |
|
||||
|
||||
```tsx
|
||||
import { useAuth } from 'payload/components/utilities'
|
||||
import { User } from '../payload-types.ts'
|
||||
'use client'
|
||||
import { useAuth } from '@payloadcms/ui'
|
||||
import type { User } from '../payload-types.ts'
|
||||
|
||||
const Greeting: React.FC = () => {
|
||||
// highlight-start
|
||||
@@ -757,10 +780,11 @@ const Greeting: React.FC = () => {
|
||||
|
||||
## useConfig
|
||||
|
||||
Used to easily fetch the full Payload config.
|
||||
Used to easily retrieve the Payload [Client Config](./components#accessing-the-payload-config).
|
||||
|
||||
```tsx
|
||||
import { useConfig } from 'payload/components/utilities'
|
||||
'use client'
|
||||
import { useConfig } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
@@ -776,7 +800,8 @@ const MyComponent: React.FC = () => {
|
||||
Sends back how many editing levels "deep" the current component is. Edit depth is relevant while adding new documents / editing documents in modal windows and other cases.
|
||||
|
||||
```tsx
|
||||
import { useEditDepth } from 'payload/components/utilities'
|
||||
'use client'
|
||||
import { useEditDepth } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
@@ -796,7 +821,8 @@ Returns methods to set and get user preferences. More info can be found [here](h
|
||||
Returns the currently selected theme (`light`, `dark` or `auto`), a set function to update it and a boolean `autoMode`, used to determine if the theme value should be set automatically based on the user's device preferences.
|
||||
|
||||
```tsx
|
||||
import { useTheme } from 'payload/components/utilities'
|
||||
'use client'
|
||||
import { useTheme } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
@@ -824,7 +850,8 @@ const MyComponent: React.FC = () => {
|
||||
Returns methods to manipulate table columns
|
||||
|
||||
```tsx
|
||||
import { useTableColumns } from 'payload/components/hooks'
|
||||
'use client'
|
||||
import { useTableColumns } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
@@ -855,7 +882,8 @@ The `useDocumentEvents` hook provides a way of subscribing to cross-document eve
|
||||
**Example:**
|
||||
|
||||
```tsx
|
||||
import { useDocumentEvents } from 'payload/components/hooks'
|
||||
'use client'
|
||||
import { useDocumentEvents } from '@payloadcms/ui'
|
||||
|
||||
const ListenForUpdates: React.FC = () => {
|
||||
const { mostRecentUpdate } = useDocumentEvents()
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
title: The Admin Panel
|
||||
label: Overview
|
||||
order: 10
|
||||
desc: Manage your data and customize the Admin Panel by swapping in your own React components. Create, modify or remove views, fields, styles and much more.
|
||||
desc: Manage your data and customize the Payload Admin Panel by swapping in your own React components. Create, modify or remove views, fields, styles and much more.
|
||||
keywords: admin, components, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Payload dynamically generates a beautiful, fully functional Admin Panel to manage your users and data. The Payload Admin Panel is highly performant, even with 100+ fields, and is written fully in TypeScript. It is built with [React](https://react.dev) using the [Next.js App Router](https://nextjs.org/docs/app) and fully supports [React Server Components](https://react.dev/reference/rsc/server-components) which enables the use of the [Local API](/docs/local-api/overview) on the front-end.
|
||||
Payload dynamically generates a beautiful, [fully type-safe](../typescript/overview) admin panel to manage your users and data. It is highly performant, even with 100+ fields, and is translated in over 30 languages. Within the Admin Panel you can manage content, [render your site](../live-preview/overview), preview drafts, [diff versions](../versions/overview), and so much more.
|
||||
|
||||
You can endlessly customize and extend the Admin UI by swapping your own in [Custom Components](./components) for everything from field labels to entire views. You can also modify built-in views, build your own fields, [swap out Payload branding for your own](https://payloadcms.com/blog/white-label-admin-ui), and so much more.
|
||||
The Admin Panel is designed to [white-label your brand](https://payloadcms.com/blog/white-label-admin-ui). You can endlessly customize and extend the Admin UI by swapping in your own [Custom Components](./components)—everything from simple field labels to entire views can be modified or replaced to meet the needs of your editors.
|
||||
|
||||
The Admin Panel is written in [TypeScript](https://www.typescriptlang.org) and built with [React](https://react.dev) using the [Next.js App Router](https://nextjs.org/docs/app). It supports [React Server Components](https://react.dev/reference/rsc/server-components) and the use of the [Local API](/docs/local-api/overview) on the front-end. You can install Payload into any [existing Next.js app in just one line](../getting-started/installation) and [deploy it anywhere](../production).
|
||||
|
||||
<Banner type="success">
|
||||
The Admin Panel is meant to be simple enough to give you a starting point but not bring too much
|
||||
complexity, so that you can easily customize it to suit the needs of your application and your
|
||||
editors.
|
||||
The Payload Admin Panel is designed to be as minimal and straightforward as possible to allow easy customization and control. [Learn more](./components).
|
||||
</Banner>
|
||||
|
||||
<LightDarkImage
|
||||
@@ -23,106 +23,209 @@ You can endlessly customize and extend the Admin UI by swapping your own in [Cus
|
||||
caption="Redesigned Admin Panel with a collapsible sidebar that's open by default, providing greater extensibility and enhanced horizontal real estate."
|
||||
/>
|
||||
|
||||
## Project Structure
|
||||
|
||||
The Admin Panel serves as the entire HTTP layer for Payload, providing a full CRUD interface for your app. This means that both the [REST](../rest-api/overview) and [GraphQL](../graphql/overview) APIs are Next.js routes that exist directly alongside your front-end application.
|
||||
|
||||
Once you [install Payload](../getting-started/installation), the following files and directories will be created in your app:
|
||||
|
||||
```plaintext
|
||||
app/
|
||||
├─ (payload)/
|
||||
├── admin/
|
||||
├─── [[...segments]]/
|
||||
├──── page.tsx
|
||||
├──── not-found.tsx
|
||||
├── api/
|
||||
├─── [...slug]/
|
||||
├──── route.ts
|
||||
├── graphql/
|
||||
├──── route.ts
|
||||
├── graphql-playground/
|
||||
├──── route.ts
|
||||
├── custom.scss
|
||||
├── layout.tsx
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
If you are not familiar with Next.js project structure, you can [learn more about it here](https://nextjs.org/docs/getting-started/project-structure).
|
||||
</Banner>
|
||||
|
||||
As shown above, all Payload routes are nested within the `(payload)` route group. This creates a boundary between the Admin Panel and the rest of your application by scoping all layouts and styles. The `layout.tsx` file within this directory, for example, is where Payload manages the `html` tag of the document to set proper `lang` and `dir` attributes, etc.
|
||||
|
||||
The `admin` directory contains all the _pages_ related to the interface itself, whereas the `api` and `graphql` directories contains all the _routes_ related to the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview). All admin routes are [easily configurable](#customizing-routes) to meet your application's requirements.
|
||||
|
||||
Finally, the `custom.scss` file is where you can add or override globally-oriented styles in the Admin Panel, such as the color palette. Customizing the look and feel through CSS alone is a powerful feature of the Admin Panel, [more on that here](./customizing-css).
|
||||
|
||||
All auto-generated files will contain the following comments at the top of each file:
|
||||
|
||||
```tsx
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */,
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
```
|
||||
|
||||
## Admin Options
|
||||
|
||||
All high-level options for the Admin Panel are defined in your Payload config under the `admin` key:
|
||||
All options for the Admin Panel are defined in your [Payload Config](../configuration/overview) under the `admin` key.
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: { // highlight-line
|
||||
// ...
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The following options are available for the Admin Panel:
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `user` | The `slug` of a Collection that you want to be used to log in to the Admin Panel. [More](/docs/admin/overview#the-admin-user-collection) |
|
||||
| `user` | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
|
||||
| `buildPath` | Specify an absolute path for where to store the built Admin bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
|
||||
| `meta` | Base meta data to use for the Admin Panel. Included properties are `titleSuffix`, `icons`, and `openGraph`. Can be overridden on a per collection or per global basis. |
|
||||
| `meta` | Base metadata to use for the Admin Panel. Included properties are `titleSuffix`, `icons`, and `openGraph`. Can be overridden on a per Collection or per Global basis. |
|
||||
| `disable` | If set to `true`, the entire Admin Panel will be disabled. |
|
||||
| `dateFormat` | Global date format that will be used for all dates in the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `dateFormat` | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
|
||||
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
|
||||
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More](/docs/authentication/config). |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More](/docs/live-preview/overview). |
|
||||
| `components` | Component overrides that affect the entirety of the Admin Panel. [More](/docs/admin/components) |
|
||||
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More](#custom-admin-panel-routes) |
|
||||
| `autoLogin` | Used to automate admin log-in for dev and demonstration convenience. [More details](../authentication/config). |
|
||||
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
|
||||
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](./components). |
|
||||
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
|
||||
| `custom` | Any custom properties you wish to pass to the Admin Panel. |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong>
|
||||
These are the _root-level_ options for the Admin Panel. You can also customize the admin options of any Collection or Global through their respective `admin` keys.
|
||||
</Banner>
|
||||
|
||||
### The Admin User Collection
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
The Admin Panel can only be used by a single auth-enabled Collection. To enable authentication for a Collection, simply set `auth: true` in the Collection's configuration. See [Authentication](/docs/authentication/overview) for more information.
|
||||
</Banner>
|
||||
|
||||
To specify which Collection to allow to login to the Admin Panel, pass the `admin.user` key equal to the slug of any auth-enabled Collection. See [Authentication](/docs/authentication/overview) for more information.
|
||||
|
||||
`payload.config.js`:
|
||||
To specify which Collection to allow to login to the Admin Panel, pass the `admin.user` key equal to the slug of any auth-enabled Collection:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
user: 'admins', // highlight-line
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
By default, if you have not specified a Collection, Payload will automatically provide a `User` Collection with access the Admin Panel. You can customize or override the fields and settings of the default `User` Collection by adding your own Collection with `slug: 'users'`. Doing this will force Payload to use your provided `User` Collection instead of its default version.
|
||||
</Banner>
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
You can use whatever Collection you'd like to access the Admin Panel as long as the Collection supports [Authentication](/docs/authentication/overview). It doesn't need to be called `users`. For example, you could use a Collection called `admins` or `editors` instead.
|
||||
The Admin Panel can only be used by a single auth-enabled Collection. To enable authentication for a Collection, simply set `auth: true` in the Collection's configuration. See [Authentication](../authentication/overview) for more information.
|
||||
</Banner>
|
||||
|
||||
For example, you may wish to have two Collections that both support Authentication:
|
||||
By default, if you have not specified a Collection, Payload will automatically provide a `User` Collection with access to the Admin Panel. You can customize or override the fields and settings of the default `User` Collection by adding your own Collection with `slug: 'users'`. Doing this will force Payload to use your provided `User` Collection instead of its default version.
|
||||
|
||||
You can use whatever Collection you'd like to access the Admin Panel as long as the Collection supports [Authentication](/docs/authentication/overview). It doesn't need to be called `users`. For example, you may wish to have two Collections that both support authentication:
|
||||
|
||||
- `admins` - meant to have a higher level of permissions to manage your data and access the Admin Panel
|
||||
- `customers` - meant for end users of your app that should not be allowed to log into the Admin Panel
|
||||
|
||||
This is totally possible. For the above scenario, by specifying `admin: { user: 'admins' }`, your Admin Panel will use `admins`. Any users logged in as `customers` will not be able to log in via the Admin Panel.
|
||||
To do this, specify `admin: { user: 'admins' }` in your config. This will provide access to the Admin Panel to only `admins`. Any users authenticated as `customers` will be prevented from accessing the Admin Panel. See [Access Control](/docs/access-control/overview) for full details.
|
||||
|
||||
### Restricting user access
|
||||
### Role-based Access Control
|
||||
|
||||
It is also possible to allow _multiple admin user types_ into the Admin Panel with limited permissions. To do this, add a `roles` or similar field to your auth-enabled Collection and use the `access.admin` property to limit access. See [Access Control](/docs/access-control/overview) for full details. For a working example, check out the [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth/payload).
|
||||
It is also possible to allow multiple user types into the Admin Panel with limited permissions. For example, you may wish to have two roles within the `admins` Collection:
|
||||
|
||||
### I18n
|
||||
- `super-admin` - full access to the Admin Panel to perform any action
|
||||
- `editor` - limited access to the Admin Panel to only manage content
|
||||
|
||||
The Payload Admin Panel is translated in over 30 languages and counting. Languages are automatically detected based on the user's browser and all text displays in that language. If no language was detected, or if the user's language is not yet supported, English will be chosen. Users can easily specify their language by selecting one from their account page. See [I18n](../configuration/i18n) for more information.
|
||||
To do this, add a `roles` or similar field to your auth-enabled Collection, then use the `access.admin` property to grant or deny access based on the value of that field. See [Access Control](/docs/access-control/overview) for full details. For a complete, working example of role-based access control, check out the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/auth/payload).
|
||||
|
||||
<Banner>
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
If there is a language that Payload does not yet support, we accept code
|
||||
[contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
|
||||
</Banner>
|
||||
## Customizing Routes
|
||||
|
||||
### Light and dark modes
|
||||
You have full control over the routes that Payload binds itself to. This includes both [Root-level Routes](#root-level-routes) such as the [REST API](../rest-api/overview), and [Admin-level Routes](#admin-level-routes) such as the user's account page. You can customize these routes to meet the needs of your application simply by specifying the desired paths in your config.
|
||||
|
||||
Users in the Admin Panel have access to choosing between light mode and dark mode for their editing experience. The setting is managed while logged into the admin UI within the user account page and will be stored with the browser. By default, the operating system preference is detected and used.
|
||||
### Root-level Routes
|
||||
|
||||
### Custom admin panel routes
|
||||
Root-level routes are those that are not behind the `/admin` path, such as the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview), or the root path of the Admin Panel itself.
|
||||
|
||||
You can configure custom routes in the Admin Panel for the following routes:
|
||||
|
||||
| Option | Default route |
|
||||
| ----------------- | ----------------------- |
|
||||
| `account` | `/account` |
|
||||
| `createFirstUser` | `/create-first-user` |
|
||||
| `forgot` | `/forgot` |
|
||||
| `inactivity` | `/logout-inactivity` |
|
||||
| `login` | `/login` |
|
||||
| `logout` | `/logout` |
|
||||
| `reset` | `/reset` |
|
||||
| `unauthorized` | `/unauthorized` |
|
||||
|
||||
`payload.config.js`:
|
||||
Here is an example of how you might modify root-level routes:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
routes: {
|
||||
admin: '/custom-admin-route' // highlight-line
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
You can configure custom paths for the following root-level routes through the `routes` property of your [Payload Config](../configuration/overview):
|
||||
|
||||
| Option | Default route | Description |
|
||||
| ------------------ | ----------------------- | ------------------------------------- |
|
||||
| `admin` | `/admin` | The Admin Panel itself. |
|
||||
| `api` | `/api` | The REST API base path. |
|
||||
| `graphQL` | `/graphql` | The GraphQL API base path. |
|
||||
| `graphQLPlayground`| `/graphql-playground` | The GraphQL Playground. |
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Reminder:</strong>
|
||||
The `routes` key is defined in the _top-level_ of your [Payload Config](../configuration/overview), _outside_ the `admin` key. To customize the Admin Panel routes, use [admin-level routes](#admin-level-routes) instead.
|
||||
</Banner>
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong>
|
||||
You can easily add _new_ routes to the Admin Panel through the `endpoints` property of the Payload Config. See [Custom Endpoints](../rest-api/overview#custom-endpoints) for more information.
|
||||
</Banner>
|
||||
|
||||
### Admin-level Routes
|
||||
|
||||
Admin-level routes are those behind the `/admin` path. These are the routes that are part of the Admin Panel itself, such as the user's account page, the login page, etc.
|
||||
|
||||
Here is an example of how you might modify admin-level routes:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
routes: {
|
||||
admin: '/custom-admin-route'
|
||||
account: '/my-account' // highlight-line
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can configure custom paths for the following admin-level routes through the `admin.routes` property of your [Payload Config](../configuration/overview):
|
||||
|
||||
| Option | Default route | Description |
|
||||
| ----------------- | ----------------------- | ----------------------------------------------- |
|
||||
| `account` | `/account` | The user's account page. |
|
||||
| `createFirstUser` | `/create-first-user` | The page to create the first user. |
|
||||
| `forgot` | `/forgot` | The password reset page. |
|
||||
| `inactivity` | `/logout-inactivity` | The page to redirect to after inactivity. |
|
||||
| `login` | `/login` | The login page. |
|
||||
| `logout` | `/logout` | The logout page. |
|
||||
| `reset` | `/reset` | The password reset page. |
|
||||
| `unauthorized` | `/unauthorized` | The unauthorized page. |
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong>
|
||||
You can also swap out entire _views_ out for your own, using the `admin.views` property of the Payload Config. See [Custom Views](./views) for more information.
|
||||
</Banner>
|
||||
|
||||
## I18n
|
||||
|
||||
The Payload Admin Panel is translated in over [30 languages and counting](https://github.com/payloadcms/payload/tree/beta/packages/translations). Languages are automatically detected based on the user's browser and used by the Admin Panel to display all text in that language. If no language was detected, or if the user's language is not yet supported, English will be chosen. Users can easily specify their language by selecting one from their account page. See [I18n](../configuration/i18n) for more information.
|
||||
|
||||
<Banner>
|
||||
<strong>Note:</strong>
|
||||
If there is a language that Payload does not yet support, we accept code
|
||||
[contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
|
||||
</Banner>
|
||||
|
||||
## Light and Dark Modes
|
||||
|
||||
Users in the Admin Panel have the ability to choose between light mode and dark mode for their editing experience. Users can select their preferred theme from their account page. Once selected, it is saved to their user's preferences and persisted across sessions and devices. If no theme was selected, the Admin Panel will automatically detect the operation system's theme and use that as the default.
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
title: Managing User Preferences
|
||||
label: Preferences
|
||||
order: 50
|
||||
desc: Store the preferences of your users as they interact with the Admin panel.
|
||||
desc: Store the preferences of your users as they interact with the Admin Panel.
|
||||
keywords: admin, preferences, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
As your users interact with your Admin panel, you might want to store their preferences in a persistent manner, so that when they revisit the Admin panel, they can pick right back up where they left off.
|
||||
As your users interact with the [Admin Panel](./overview), you might want to store their preferences in a persistent manner, so that when they revisit the Admin Panel in a different session or from a different device, they can pick right back up where they left off.
|
||||
|
||||
Out of the box, Payload handles the persistence of your users' preferences in a handful of ways, including:
|
||||
|
||||
1. Collection `List` view active columns, and their order, that users define
|
||||
1. Their last active locale
|
||||
1. The "collapsed" state of blocks, on a document level, as users edit or interact with documents
|
||||
1. Columns in the Collection List View: their active state and order
|
||||
1. The user's last active [Locale](../configuration/localization)
|
||||
1. The "collapsed" state of `blocks`, `array`, and `collapsible` fields
|
||||
1. The last-known state of the `Nav` component, etc.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
@@ -23,7 +24,7 @@ Out of the box, Payload handles the persistence of your users' preferences in a
|
||||
|
||||
## Use cases
|
||||
|
||||
This API is used significantly for internal operations of the Admin panel, as mentioned above. But, if you're building your own React components for use in the Admin panel, you can allow users to set their own preferences in correspondence to their usage of your components. For example:
|
||||
This API is used significantly for internal operations of the Admin Panel, as mentioned above. But, if you're building your own React components for use in the Admin Panel, you can allow users to set their own preferences in correspondence to their usage of your components. For example:
|
||||
|
||||
- If you have built a "color picker", you could "remember" the last used colors that the user has set for easy access next time
|
||||
- If you've built a custom `Nav` component, and you've built in an "accordion-style" UI, you might want to store the `collapsed` state of each Nav collapsible item. This way, if an editor returns to the panel, their `Nav` state is persisted automatically
|
||||
@@ -32,14 +33,14 @@ This API is used significantly for internal operations of the Admin panel, as me
|
||||
|
||||
## Database
|
||||
|
||||
Payload automatically creates an internally used `payload-preferences` collection that stores user preferences. Each document in the `payload-preferences` collection contains the following shape:
|
||||
Payload automatically creates an internally used `payload-preferences` Collection that stores user preferences. Each document in the `payload-preferences` Collection contains the following shape:
|
||||
|
||||
| Key | Value |
|
||||
| ----------------- | ----------------------------------------------------------------- |
|
||||
| `id` | A unique ID for each preference stored. |
|
||||
| `key` | A unique `key` that corresponds to the preference. |
|
||||
| `user.value` | The ID of the `user` that is storing its preference. |
|
||||
| `user.relationTo` | The `slug` of the collection that the `user` is logged in as. |
|
||||
| `user.relationTo` | The `slug` of the Collection that the `user` is logged in as. |
|
||||
| `value` | The value of the preference. Can be any data shape that you need. |
|
||||
| `createdAt` | A timestamp of when the preference was created. |
|
||||
| `updatedAt` | A timestamp set to the last time the preference was updated. |
|
||||
@@ -50,7 +51,7 @@ Preferences are available to both [GraphQL](/docs/graphql/overview#preferences)
|
||||
|
||||
## Adding or reading Preferences in your own components
|
||||
|
||||
The Payload admin panel offers a `usePreferences` hook. The hook is only meant for use within the admin panel itself. It provides you with two methods:
|
||||
The Payload Admin Panel offers a `usePreferences` hook. The hook is only meant for use within the Admin Panel itself. It provides you with two methods:
|
||||
|
||||
#### `getPreference`
|
||||
|
||||
@@ -71,11 +72,12 @@ Also async, this method provides you with an easy way to set a user preference.
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example for how you can utilize `usePreferences` within your custom Admin panel components. Note - this example is not fully useful and is more just a reference for how to utilize the Preferences API. In this case, we are demonstrating how to set and retrieve a user's last used colors history within a `ColorPicker` or similar type component.
|
||||
Here is an example for how you can utilize `usePreferences` within your custom Admin Panel components. Note - this example is not fully useful and is more just a reference for how to utilize the Preferences API. In this case, we are demonstrating how to set and retrieve a user's last used colors history within a `ColorPicker` or similar type component.
|
||||
|
||||
```
|
||||
```tsx
|
||||
'use client'
|
||||
import React, { Fragment, useState, useEffect, useCallback } from 'react';
|
||||
import { usePreferences } from 'payload/components/preferences';
|
||||
import { usePreferences } from '@payloadcms/ui'
|
||||
|
||||
const lastUsedColorsPreferenceKey = 'last-used-colors';
|
||||
|
||||
|
||||
@@ -6,32 +6,45 @@ desc:
|
||||
keywords:
|
||||
---
|
||||
|
||||
### Root
|
||||
Views are the individual pages that make up the [Admin Panel](./overview), such as the Dashboard, List, and Edit views. One of the most powerful ways to customize the Admin Panel is to create Custom Views. These are [Custom Components](./components) that can either replace built-in views or be entirely new.
|
||||
|
||||
You can easily swap entire views with your own by using the `admin.components.views` property. At the root level, Payload renders the following views by default, all of which can be overridden:
|
||||
To swap in your own Custom Views, consult the list of available components below. Determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-views) accordingly.
|
||||
|
||||
There are four types of views in Payload:
|
||||
|
||||
- [Root Views](#custom-root-views)
|
||||
- [Collection Views](#custom-collection-views)
|
||||
- [Global Views](#custom-global-views)
|
||||
- [Document Views](#custom-document-views)
|
||||
|
||||
## Custom Root Views
|
||||
|
||||
Root Views are the main views of the [Admin Panel](./overview). These are views that are scoped directly under the `/admin` route, such as the Dashboard or Account views. You can easily swap Root Views with your own or [create entirely new ones](#adding-new-root-views) through the `admin.components.views` property of your [Payload Config](../configuration/overview).
|
||||
|
||||
Here is an example showing what it might look like to swap out Root Views for your own Custom Views. See [Building Custom Views](#building-custom-views) for exact details on how to build them:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Dashboard: MyCustomDashboardView, // highlight-line
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Property | Description |
|
||||
| --------------- | ----------------------------------------------------------------------------- |
|
||||
| **`Account`** | The Account view is used to show the currently logged in user's Account page. |
|
||||
| **`Dashboard`** | The main landing page of the [Admin Panel](./overview). |
|
||||
|
||||
To swap out any of these views, simply pass in your custom component to the `admin.components.views` property of your Payload config. For example:
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Account: MyCustomAccountView,
|
||||
Dashboard: MyCustomDashboardView,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For more granular control, pass a configuration object instead. Payload exposes the following properties for each view:
|
||||
|
||||
| Property | Description |
|
||||
@@ -39,227 +52,170 @@ For more granular control, pass a configuration object instead. Payload exposes
|
||||
| **`Component`** \* | Pass in the component that should be rendered when a user navigates to this route. |
|
||||
| **`path`** \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
|
||||
| **`exact`** | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
|
||||
| **`strict`** | When true, a path that has a trailing slash will only match a location.pathname with a trailing slash. This has no effect when there are additional URL segments in the location.pathname. |
|
||||
| **`strict`** | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
|
||||
| **`sensitive`** | When true, will match if the path is case sensitive. |
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
#### Adding new root views
|
||||
### Adding New Views
|
||||
|
||||
To add a _new_ view to the [Admin Panel](./overview), simply add another key to the `views` object with at least a `path` and `Component` property. For example:
|
||||
To add a _new_ views to the [Admin Panel](./overview), simply add your own key to the `views` object with at least a `path` and `Component` property. For example:
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
{
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
// highlight-start
|
||||
MyCustomView: {
|
||||
// highlight-end
|
||||
Component: MyCustomView,
|
||||
path: '/my-custom-view',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The above example shows how to add a new [Root View](#custom-root-views), but the pattern is the same for [Collection Views](#custom-collection-views), [Global Views](#custom-global-views), and [Document Views](#custom-document-views). For help on how to build your own Custom Views, see [Building Custom Views](#building-custom-views).
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
Routes are cascading, so unless explicitly given the `exact` property, they will
|
||||
match on URLs that simply _start_ with the route's path. This is helpful when creating catch-all
|
||||
routes in your application. Alternatively, define your nested route _before_ your parent
|
||||
route.
|
||||
</Banner>
|
||||
|
||||
## Custom Collection Views
|
||||
|
||||
Collection Views are views that are scoped under the `/collections` route, such as the Collection List and Document Edit views. You can easily swap out Collection Views with your own or [create entirely new ones](#adding-new-views), through the `admin.components.views` property of your [Collection Config](../collections/overview).
|
||||
|
||||
Here is an example showing what it might look like to swap out Collection Views for your own Custom Views. See [Building Custom Views](#building-custom-views) for exact details on how to build them:
|
||||
|
||||
```ts
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: MyCustomEditView, // highlight-line
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
Routes are cascading. This means that unless explicitly given the `exact` property, they will
|
||||
match on URLs that simply _start_ with the route's path. This is helpful when creating catch-all
|
||||
routes in your application. Alternatively, you could define your nested route _before_ your parent
|
||||
route.
|
||||
The `Edit` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#custom-document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `Edit.Default` key instead.
|
||||
</Banner>
|
||||
|
||||
For help on how to build your own custom view components, see [Building Custom View Components](#building-custom-view-components). For more examples regarding how to customize components, [look at the following examples](https://github.com/payloadcms/payload/tree/main/test/admin/components)._
|
||||
The following options are available:
|
||||
|
||||
### Collections
|
||||
| Property | Description |
|
||||
| ---------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Edit`** | The Edit View is used to edit a single document for any given Collection. [More details](#custom-document-views). |
|
||||
| **`List`** | The List View is used to show a list of documents for any given Collection. |
|
||||
|
||||
To swap out entire views on collections, you can use the `admin.components.views` property on the collection's config. Payload renders the following views by default, all of which can be overridden:
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong>
|
||||
You can also add _new_ Collection Views to the config by adding a new key to the `views` object with at least a `path` and `Component` property. See [Adding New Views](#adding-new-views) for more information.
|
||||
</Banner>
|
||||
|
||||
| Property | Description |
|
||||
| ---------- | ------------------------------------------------------------------------- |
|
||||
| **`Edit`** | The Edit view is used to edit a single document for a given collection. |
|
||||
| **`List`** | The List view is used to show a list of documents for a given collection. |
|
||||
## Custom Global Views
|
||||
|
||||
To swap out any of these views, simply pass in your custom component to the `admin.components.views` property of your Payload config. This will replace the entire view, including the page breadcrumbs, title, tabs, etc, _as well as all nested routes_.
|
||||
Global Views are views that are scoped under the `/globals` route, such as the Document Edit View. You can easily swap out Global Views with your own or [create entirely new ones](#adding-new-views), through the `admin.components.views` property of your [Global Config](../globals/overview).
|
||||
|
||||
Here is an example showing what it might look like to swap out Global Views for your own Custom Views. See [Building Custom Views](#building-custom-views) for exact details on how to build them:
|
||||
|
||||
```ts
|
||||
// Collection.ts
|
||||
{
|
||||
import type { SanitizedGlobalConfig } from 'payload/types'
|
||||
|
||||
export const MyGlobalConfig: SanitizedGlobalConfig = {
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: MyCustomEditView,
|
||||
List: MyCustomListView,
|
||||
Edit: MyCustomEditView, // highlight-line
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
For help on how to build your own custom view components, see [Building Custom View Components](#building-custom-view-components).
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
The `Edit` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#custom-document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `Edit.Default` key instead.
|
||||
</Banner>
|
||||
|
||||
**Customizing Nested Views within 'Edit' in Collections**
|
||||
|
||||
The `Edit` view in collections consists of several nested views, each serving a unique purpose. You can customize these nested views using the `admin.components.views.Edit` property in the collection's configuration. This approach allows you to replace specific nested views while keeping the overall structure of the `Edit` view intact, including the page breadcrumbs, title, tabs, etc.
|
||||
|
||||
Here's an example of how you can customize nested views within the `Edit` view in collections, including the use of the `actions` property:
|
||||
|
||||
```ts
|
||||
// Collection.ts
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: {
|
||||
Default: {
|
||||
Component: MyCustomDefaultTab,
|
||||
actions: [CollectionEditButton], // Custom actions for the default edit view
|
||||
},
|
||||
API: {
|
||||
Component: MyCustomAPIView,
|
||||
actions: [CollectionAPIButton], // Custom actions for API view
|
||||
},
|
||||
LivePreview: {
|
||||
Component: MyCustomLivePreviewView,
|
||||
actions: [CollectionLivePreviewButton], // Custom actions for Live Preview
|
||||
},
|
||||
Version: {
|
||||
Component: MyCustomVersionView,
|
||||
actions: [CollectionVersionButton], // Custom actions for Version view
|
||||
},
|
||||
Versions: {
|
||||
Component: MyCustomVersionsView,
|
||||
actions: [CollectionVersionsButton], // Custom actions for Versions view
|
||||
},
|
||||
},
|
||||
List: {
|
||||
actions: [CollectionListButton],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Adding New Tabs to 'Edit' View**
|
||||
|
||||
You can also add _new_ tabs to the `Edit` view by adding another key to the `components.views.Edit[key]` object with a `path` and `Component` property. See [Custom Tabs](#custom-tabs) for more information.
|
||||
|
||||
### Globals
|
||||
|
||||
To swap out views for globals, you can use the `admin.components.views` property on the global's config. Payload renders the following views by default, all of which can be overridden:
|
||||
The following options are available:
|
||||
|
||||
| Property | Description |
|
||||
| ---------- | ------------------------------------------------------------------- |
|
||||
| **`Edit`** | The Edit view is used to edit a single document for a given Global. |
|
||||
| **`Edit`** | The Edit View is used to edit a single document for any given Global. [More details](#custom-document-views). |
|
||||
|
||||
To swap out any of these views, simply pass in your custom component to the `admin.components.views` property of your Payload config. This will replace the entire view, including the page breadcrumbs, title, and tabs, _as well as all nested views_.
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong>
|
||||
You can also add _new_ Global Views to the config by adding a new key to the `views` object with at least a `path` and `Component` property. See [Adding New Views](#adding-new-views) for more information.
|
||||
</Banner>
|
||||
|
||||
## Custom Document Views
|
||||
|
||||
Document Views are views that are scoped under the `/collections/:collectionSlug/:id` or the `/globals/:globalSlug` route, such as the Edit View. You can easily swap out Document Views with your own or [create entirely new ones](#adding-new-document-views) through the `admin.components.views.Edit[key]` property of either your [Collection Config](../collections/overview) or [Global Config](../globals/overview).
|
||||
|
||||
This approach allows you to replace specific nested views while keeping the overall structure of the Edit View intact, including the title, tabs, etc. If you need to replace the _entire_ Edit View, including the nested Document Views, use the `Edit` key itself. See [Custom Collection Views](#custom-collection-views) or [Custom Global Views](#custom-global-views) for more information.
|
||||
|
||||
Here's an example showing what it might look like to swap out Document Views for your own Custom Views. See [Building Custom Views](#building-custom-views) for exact details on how to build them:
|
||||
|
||||
```ts
|
||||
// Global.ts
|
||||
{
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: MyCustomEditView,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
For help on how to build your own custom view components, see [Building Custom View Components](#building-custom-view-components).
|
||||
|
||||
**Customizing Nested Views within 'Edit' in Globals**
|
||||
|
||||
Similar to collections, Globals allow for detailed customization within the `Edit` view. This includes the ability to swap specific nested views while maintaining the overall structure of the `Edit` view. You can use the `admin.components.views.Edit` property in the Globals configuration to achieve this, and this will only replace the nested view, leaving the page breadcrumbs, title, and tabs intact.
|
||||
|
||||
Here's how you can customize nested views within the `Edit` view in Globals, including the use of the `actions` property:
|
||||
|
||||
```ts
|
||||
// Global.ts
|
||||
{
|
||||
export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: {
|
||||
Default: {
|
||||
Component: MyCustomGlobalDefaultTab,
|
||||
actions: [GlobalEditButton], // Custom actions for the default edit view
|
||||
},
|
||||
API: {
|
||||
Component: MyCustomGlobalAPIView,
|
||||
actions: [GlobalAPIButton], // Custom actions for API view
|
||||
},
|
||||
LivePreview: {
|
||||
Component: MyCustomGlobalLivePreviewView,
|
||||
actions: [GlobalLivePreviewButton], // Custom actions for Live Preview
|
||||
},
|
||||
Version: {
|
||||
Component: MyCustomGlobalVersionView,
|
||||
actions: [GlobalVersionButton], // Custom actions for Version view
|
||||
},
|
||||
Versions: {
|
||||
Component: MyCustomGlobalVersionsView,
|
||||
actions: [GlobalVersionsButton], // Custom actions for Versions view
|
||||
Component: MyCustomAPIView, // highlight-line
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
You can also add _new_ tabs to the `Edit` view by adding another key to the `components.views.Edit[key]` object with a `path` and `Component` property. See [Custom Tabs](#custom-tabs) for more information.
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
This applies to _both_ Collection _and_ Global configs.
|
||||
</Banner>
|
||||
|
||||
### Custom Tabs
|
||||
|
||||
You can easily swap individual collection or global edit views. To do this, pass an _object_ to the `admin.components.views.Edit` property of the config. Payload renders the following views by default, all of which can be overridden:
|
||||
The following options are available:
|
||||
|
||||
| Property | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Default`** | The Default view is the primary view in which your document is edited. |
|
||||
| **`Versions`** | The Versions view is used to view the version history of a single document. [More details](../versions) |
|
||||
| **`Versions`** | The Versions view is used to view the version history of a single document. [More details](../versions). |
|
||||
| **`Version`** | The Version view is used to view a single version of a single document for a given collection. [More details](../versions). |
|
||||
| **`API`** | The API view is used to display the REST API JSON response for a given document. |
|
||||
| **`LivePreview`** | The LivePreview view is used to display the Live Preview interface. [More details](../live-preview) |
|
||||
| **`LivePreview`** | The LivePreview view is used to display the Live Preview interface. [More details](../live-preview). |
|
||||
|
||||
Here is an example:
|
||||
### Document Tabs
|
||||
|
||||
Each Document View can be given a new tab in the Edit View, if desired. Tabs are highly configurable, from as simple as changing the label to swapping out the entire component, they can be modified in any way. To add or customize tabs in the Edit View, use the `Component.Tab` key:
|
||||
|
||||
```ts
|
||||
// Collection.ts or Global.ts
|
||||
export const MyCollection: SanitizedCollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: {
|
||||
// You can also define `components.views.Edit` as a component, this will override _all_ nested views
|
||||
Default: MyCustomDefaultTab,
|
||||
Versions: MyCustomVersionsTab,
|
||||
Version: MyCustomVersionTab,
|
||||
API: MyCustomAPITab,
|
||||
LivePreview: MyCustomLivePreviewTab,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
|
||||
To add a _new_ tab to the `Edit` view, simply add another key to `components.views.Edit[key]` with at least a `path` and `Component` property. For example:
|
||||
|
||||
```ts
|
||||
// `Collection.ts` or `Global.ts`
|
||||
export const MyCollection: SanitizedCollectionConfig = {
|
||||
slug: 'my-collection',
|
||||
admin: {
|
||||
@@ -269,17 +225,17 @@ export const MyCollection: SanitizedCollectionConfig = {
|
||||
MyCustomTab: {
|
||||
Component: MyCustomTab,
|
||||
path: '/my-custom-tab',
|
||||
// You an swap the entire tab component out for your own
|
||||
Tab: MyCustomTab,
|
||||
Tab: MyCustomTab // highlight-line
|
||||
},
|
||||
AnotherCustomView: {
|
||||
Component: AnotherCustomView,
|
||||
path: '/another-custom-view',
|
||||
// Or you can use the default tab component and just pass in your own label and href
|
||||
// highlight-start
|
||||
Tab: {
|
||||
label: 'Another Custom View',
|
||||
href: '/another-custom-view',
|
||||
},
|
||||
}
|
||||
// highlight-end
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -288,14 +244,44 @@ export const MyCollection: SanitizedCollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
### Building Custom View Components
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
This applies to _both_ Collections _and_ Globals.
|
||||
</Banner>
|
||||
|
||||
Your custom view components will be provided with the following props:
|
||||
### Building Custom Views
|
||||
|
||||
| Prop | Description |
|
||||
| ----------------------- | ---------------------------------------------------------------------------- |
|
||||
| **`user`** | The currently logged in user. Will be `null` if no user is logged in. |
|
||||
| **`canAccessAdmin`** \* | If the currently logged in user is allowed to access the [Admin Panel](./overview) or not. |
|
||||
Custom Views are just [Custom Components](./components) rendered at the page-level. To understand how to build Custom Views, first review the [Building Custom Components](./components#building-custom-components) guide. Once you have a Custom Component ready, you can use it as a Custom View.
|
||||
|
||||
```ts
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
import { MyCustomView } from './MyCustomView'
|
||||
|
||||
export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
// ...
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
Edit: MyCustomView, // highlight-line
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Your Custom Views will be provided with the following props:
|
||||
|
||||
| Prop | Description |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `payload` | The [Payload](../local-api/overview) class. |
|
||||
| `i18n` | The [i18n](../configuration/i18n) object. |
|
||||
| `user` | The currently logged in user. |
|
||||
| `locale` | The current [Locale](../configuration/localization) of the [Admin Panel](./overview). |
|
||||
| `navGroups` | The grouped navigation items according to `admin.group` in your [Collection Config](../collections/overview) or [Global Config](../globals/overview). |
|
||||
| `params` | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
|
||||
| `permissions` | The permissions of the currently logged in user. |
|
||||
| `searchParams` | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
|
||||
| `visibleEntities` | The current user's visible entities according to your [Access Control](../access-control/overview). |
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
@@ -303,13 +289,3 @@ Your custom view components will be provided with the following props:
|
||||
It's up to you to secure your custom views. If your view requires a user to be logged in or to
|
||||
have certain access rights, you should handle that within your view component yourself.
|
||||
</Banner>
|
||||
|
||||
### Examples
|
||||
|
||||
You can find examples of custom views in the [Payload source code `/test/admin/components/views` folder](https://github.com/payloadcms/payload/tree/main/test/admin/components/views). There, you'll find two custom views:
|
||||
|
||||
1. A custom view that uses the `DefaultTemplate`, which is the built-in Payload template that displays the sidebar and "eyebrow nav"
|
||||
1. A custom view that uses the `MinimalTemplate` - which is just a centered template used for things like logging in or out
|
||||
|
||||
To see how to pass in your custom views to create custom views of your own, take a look at the `admin.components.views` property of the [Payload test admin config](https://github.com/payloadcms/payload/blob/main/test/admin/config.ts).
|
||||
|
||||
|
||||
81
docs/authentication/api-keys.mdx
Normal file
81
docs/authentication/api-keys.mdx
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: API Key Strategy
|
||||
label: API Key Strategy
|
||||
order: 50
|
||||
desc: Enable API key based authentication to interface with Payload.
|
||||
keywords: authentication, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
To integrate with third-party APIs or services, you might need the ability to generate API keys that can be used to identify as a certain user within Payload. API keys are generated on a user-by-user basis, similar to email and passwords, and are meant to represent a single user.
|
||||
|
||||
For example, if you have a third-party service or external app that needs to be able to perform protected actions against Payload, first you need to create a user within Payload, i.e. `dev@thirdparty.com`. From your external application you will need to authenticate with that user, you have two options:
|
||||
|
||||
1. Log in each time with that user and receive an expiring token to request with.
|
||||
1. Generate a non-expiring API key for that user to request with.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br/>
|
||||
This is particularly useful as you can create a "user" that reflects an integration with a specific external service and assign a "role" or specific access only needed by that service/integration.
|
||||
</Banner>
|
||||
|
||||
Technically, both of these options will work for third-party integrations but the second option with API key is simpler, because it reduces the amount of work that your integrations need to do to be authenticated properly.
|
||||
|
||||
To enable API keys on a collection, set the `useAPIKey` auth option to `true`. From there, a new interface will appear in the Admin panel for each document within the collection that allows you to generate an API key for each user in the Collection.
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const ThirdPartyAccess: CollectionConfig = {
|
||||
slug: 'third-party-access',
|
||||
auth: {
|
||||
useAPIKey: true, // highlight-line
|
||||
},
|
||||
fields: [],
|
||||
}
|
||||
```
|
||||
|
||||
User API keys are encrypted within the database, meaning that if your database is compromised,
|
||||
your API keys will not be.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
If you change your `PAYLOAD_SECRET`, you will need to regenerate your API keys.
|
||||
<br />
|
||||
The secret key is used to encrypt the API keys, so if you change the secret, existing API keys will
|
||||
no longer be valid.
|
||||
</Banner>
|
||||
|
||||
### HTTP Authentication
|
||||
|
||||
To authenticate REST or GraphQL API requests using an API key, set the `Authorization` header. The header is case-sensitive and needs the slug of the `auth.useAPIKey` enabled collection, then " API-Key ", followed by the `apiKey` that has been assigned. Payload's built-in middleware will then assign the user document to `req.user` and handle requests with the proper access control. By doing this, Payload recognizes the request being made as a request by the user associated with that API key.
|
||||
|
||||
**For example, using Fetch:**
|
||||
|
||||
```ts
|
||||
import Users from '../collections/Users'
|
||||
|
||||
const response = await fetch('http://localhost:3000/api/pages', {
|
||||
headers: {
|
||||
Authorization: `${Users.slug} API-Key ${YOUR_API_KEY}`,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Payload ensures that the same, uniform access control is used across all authentication strategies. This enables you to utilize your existing access control configurations with both API keys and the standard email/password authentication. This consistency can aid in maintaining granular control over your API keys.
|
||||
|
||||
### API Key Only Auth
|
||||
|
||||
If you want to use API keys as the only authentication method for a collection, you can disable the default local strategy by setting `disableLocalStrategy` to `true` on the collection's `auth` property. This will disable the ability to authenticate with email and password, and will only allow for authentication via API key.
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const ThirdPartyAccess: CollectionConfig = {
|
||||
slug: 'third-party-access',
|
||||
auth: {
|
||||
useAPIKey: true,
|
||||
disableLocalStrategy: true, // highlight-line
|
||||
},
|
||||
}
|
||||
```
|
||||
@@ -25,68 +25,6 @@ To enable Authentication on a collection, define an `auth` property and set it t
|
||||
| **`disableLocalStrategy`** | Advanced - disable Payload's built-in local auth strategy. Only use this property if you have replaced Payload's auth mechanisms with your own. |
|
||||
| **`strategies`** | Advanced - an array of PassportJS authentication strategies to extend this collection's authentication with. [More](/docs/authentication/config#strategies) |
|
||||
|
||||
### API keys
|
||||
|
||||
To integrate with third-party APIs or services, you might need the ability to generate API keys that can be used to identify as a certain user within Payload.
|
||||
|
||||
In Payload, users are essentially documents within a collection. Just like you can authenticate as a user with an email and password, which is considered as our default local auth strategy, you can also authenticate as a user with an API key. API keys are generated on a user-by-user basis, similar to email and passwords, and are meant to represent a single user.
|
||||
|
||||
For example, if you have a third-party service or external app that needs to be able to perform protected actions at its discretion, you have two options:
|
||||
|
||||
1. Create a user for the third-party app, and log in each time to receive a token before you attempt to access any protected actions
|
||||
1. Enable API key support for the Collection, where you can generate a non-expiring API key per user in the collection. This is particularly useful as you can create a "user" that reflects an integration with a specific external service and assign a "role" or specific access only needed by that service/integration. Alternatively, you could create a "super admin" user and assign an API key to that user so that any requests made with that API key are considered as being made by that super user.
|
||||
|
||||
Technically, both of these options will work for third-party integrations but the second option with API key is simpler, because it reduces the amount of work that your integrations need to do to be authenticated properly.
|
||||
|
||||
To enable API keys on a collection, set the `useAPIKey` auth option to `true`. From there, a new interface will appear in the Admin panel for each document within the collection that allows you to generate an API key for each user in the Collection.
|
||||
|
||||
<Banner type="success">
|
||||
User API keys are encrypted within the database, meaning that if your database is compromised,
|
||||
your API keys will not be.
|
||||
</Banner>
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
If you change your `PAYLOAD_SECRET`, you will need to regenerate your API keys.
|
||||
<br />
|
||||
The secret key is used to encrypt the API keys, so if you change the secret, existing API keys will
|
||||
no longer be valid.
|
||||
</Banner>
|
||||
|
||||
#### Authenticating via API Key
|
||||
|
||||
To authenticate REST or GraphQL API requests using an API key, set the `Authorization` header. The header is case-sensitive and needs the slug of the `auth.useAPIKey` enabled collection, then " API-Key ", followed by the `apiKey` that has been assigned. Payload's built-in middleware will then assign the user document to `req.user` and handle requests with the proper access control. By doing this, Payload recognizes the request being made as a request by the user associated with that API key.
|
||||
|
||||
**For example, using Fetch:**
|
||||
|
||||
```ts
|
||||
import User from '../collections/User'
|
||||
|
||||
const response = await fetch('http://localhost:3000/api/pages', {
|
||||
headers: {
|
||||
Authorization: `${User.slug} API-Key ${YOUR_API_KEY}`,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Payload ensures that the same, uniform access control is used across all authentication strategies. This enables you to utilize your existing access control configurations with both API keys and the standard email/password authentication. This consistency can aid in maintaining granular control over your API keys.
|
||||
|
||||
#### API Key _Only_ Authentication
|
||||
|
||||
If you want to use API keys as the only authentication method for a collection, you can disable the default local strategy by setting `disableLocalStrategy` to `true` on the collection's `auth` property. This will disable the ability to authenticate with email and password, and will only allow for authentication via API key.
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const Customers: CollectionConfig = {
|
||||
slug: 'customers',
|
||||
auth: {
|
||||
useAPIKey: true,
|
||||
disableLocalStrategy: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Forgot Password
|
||||
|
||||
You can customize how the Forgot Password workflow operates with the following options on the `auth.forgotPassword` property:
|
||||
@@ -228,31 +166,6 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
### Strategies
|
||||
|
||||
As of Payload `1.0.0`, you can add additional authentication strategies to Payload easily by passing them to your collection's `auth.strategies` array.
|
||||
|
||||
Behind the scenes, Payload uses PassportJS to power its local authentication strategy, so most strategies listed on the PassportJS website will work seamlessly. Combined with adding custom components to the admin panel's `Login` view, you can create advanced authentication strategies directly within Payload.
|
||||
|
||||
<Banner type="warning">
|
||||
This is an advanced feature, so only attempt this if you are an experienced developer. Otherwise,
|
||||
just let Payload's built-in authentication handle user auth for you.
|
||||
</Banner>
|
||||
|
||||
The `strategies` property is an array that takes objects with the following properties:
|
||||
|
||||
**`strategy`**
|
||||
|
||||
This property can accept a Passport strategy directly, or you can pass a function that takes a `payload` argument, and returns a Passport strategy.
|
||||
|
||||
**`name`**
|
||||
|
||||
If you pass a strategy to the `strategy` property directly, the `name` property is optional and allows you to override the strategy's built-in name.
|
||||
|
||||
However, if you pass a function to `strategy`, `name` is a required property.
|
||||
|
||||
In either case, Payload will prefix the strategy name with the collection `slug` that the strategy is passed to.
|
||||
|
||||
### Admin autologin
|
||||
|
||||
For testing and demo purposes you may want to skip forcing the admin user to login in order to access the panel.
|
||||
|
||||
81
docs/authentication/custom-strategies.mdx
Normal file
81
docs/authentication/custom-strategies.mdx
Normal file
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: Custom Strategies
|
||||
label: Custom Strategies
|
||||
order: 60
|
||||
desc: Create custom authentication strategies to handle everything auth in Payload.
|
||||
keywords: authentication, config, configuration, overview, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
<Banner type="warning">
|
||||
This is an advanced feature, so only attempt this if you are an experienced developer. Otherwise,
|
||||
just let Payload's built-in authentication handle user auth for you.
|
||||
</Banner>
|
||||
|
||||
### Creating a strategy
|
||||
|
||||
At the core, a strategy is a way to authenticate a user making a request. As of `3.0` we moved away from passportJS in favor of pulling back the curtain and putting you in full control.
|
||||
|
||||
A strategy is made up of the following:
|
||||
|
||||
| Parameter | Description |
|
||||
| --------------------------- | ------------------------------------------------------------------------- |
|
||||
| **`name`** \* | The name of your strategy |
|
||||
| **`authenticate`** \* | A function that takes in the parameters below and returns a user or null. |
|
||||
|
||||
The `authenticate` function is passed the following arguments:
|
||||
|
||||
| Argument | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| **`headers`** \* | The headers on the incoming request. Useful for retrieving identifiable information on a request. |
|
||||
| **`payload`** \* | The Payload class. Useful for authenticating the identifiable information against Payload. |
|
||||
| **`isGraphQL`** | Whether or not the request was made from a GraphQL endpoint. Default is `false`. |
|
||||
|
||||
|
||||
### Example Strategy
|
||||
|
||||
At its core a strategy simply takes information from the incoming request and returns a user. This is exactly how Payloads built-in strategies function.
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
auth: {
|
||||
disableLocalStrategy: true,
|
||||
// highlight-start
|
||||
strategies: [
|
||||
{
|
||||
name: 'custom-strategy',
|
||||
authenticate: ({ payload, headers }) => {
|
||||
const usersQuery = await payload.find({
|
||||
collection: 'users',
|
||||
where: {
|
||||
code: {
|
||||
equals: headers.get('code'),
|
||||
},
|
||||
secret: {
|
||||
equals: headers.get('secret'),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return usersQuery.docs[0] || null
|
||||
}
|
||||
}
|
||||
]
|
||||
// highlight-end
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'code',
|
||||
type: 'text',
|
||||
index: true,
|
||||
unique: true,
|
||||
},
|
||||
{
|
||||
name: 'secret',
|
||||
type: 'text',
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
91
docs/authentication/http-only-cookies.mdx
Normal file
91
docs/authentication/http-only-cookies.mdx
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
title: Cookie Strategy
|
||||
label: Cookie Strategy
|
||||
order: 30
|
||||
desc: Enable HTTP Cookie based authentication to interface with Payload.
|
||||
keywords: authentication, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Payload `login`, `logout`, and `refresh` operations make use of HTTP-only cookies for authentication purposes.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
You can access the logged-in user from access control functions and hooks from the `req.user` property.
|
||||
<br />
|
||||
[Learn more about token data](/docs/authentication/token-data).
|
||||
</Banner>
|
||||
|
||||
### Automatic browser inclusion
|
||||
|
||||
Modern browsers automatically include `http-only` cookies when making requests directly to URLs—meaning that if you are running your API on `https://example.com`, and you have logged in and visit `https://example.com/test-page`, your browser will automatically include the Payload authentication cookie for you.
|
||||
|
||||
### HTTP Authentication
|
||||
|
||||
However, if you use `fetch` or similar APIs to retrieve Payload resources from its REST or GraphQL API, you must specify to include credentials (cookies).
|
||||
|
||||
Fetch example, including credentials:
|
||||
|
||||
```ts
|
||||
const response = await fetch('http://localhost:3000/api/pages', {
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
const pages = await response.json()
|
||||
```
|
||||
|
||||
For more about including cookies in requests from your app to your Payload API, [read the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included).
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
To make sure you have a Payload cookie set properly in your browser after logging in, you can use
|
||||
the browsers Developer Tools > Application > Cookies > [your-domain-here]. The Developer tools
|
||||
will still show HTTP-only cookies.
|
||||
</Banner>
|
||||
|
||||
### CSRF Attacks
|
||||
|
||||
CSRF (cross-site request forgery) attacks are common and dangerous. By using an HTTP-only cookie, Payload removes many XSS vulnerabilities, however, CSRF attacks can still be possible.
|
||||
|
||||
For example, let's say you have a popular app `https://payload-finances.com` that allows users to manage finances, send and receive money. As Payload is using HTTP-only cookies, that means that browsers automatically will include cookies when sending requests to your domain - <strong>no matter what page created the request</strong>.
|
||||
|
||||
So, if a user of `https://payload-finances.com` is logged in and is browsing around on the internet, they might stumble onto a page with malicious intent. Let's look at an example:
|
||||
|
||||
```ts
|
||||
// malicious-intent.com
|
||||
// makes an authenticated request as on your behalf
|
||||
|
||||
const maliciousRequest = await fetch(`https://payload-finances.com/api/me`, {
|
||||
credentials: 'include'
|
||||
}).then(res => await res.json())
|
||||
```
|
||||
|
||||
In this scenario, if your cookie was still valid, malicious-intent.com would be able to make requests like the one above on your behalf. This is a CSRF attack.
|
||||
|
||||
### CSRF Prevention
|
||||
|
||||
Define domains that your trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload config to do this:
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
serverURL: 'https://my-payload-instance.com',
|
||||
// highlight-start
|
||||
csrf: [
|
||||
// whitelist of domains to allow cookie auth from
|
||||
'https://your-frontend-app.com',
|
||||
'https://your-other-frontend-app.com',
|
||||
// `config.serverURL` is added by default if defined
|
||||
],
|
||||
// highlight-end
|
||||
collections: [
|
||||
// collections here
|
||||
],
|
||||
})
|
||||
|
||||
export default config
|
||||
```
|
||||
54
docs/authentication/jwt.mdx
Normal file
54
docs/authentication/jwt.mdx
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: JWT Strategy
|
||||
label: JWT Strategy
|
||||
order: 40
|
||||
desc: Enable JSON Web Token based authentication to interface with Payload.
|
||||
keywords: authentication, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Payload offers the ability to authenticate via `JWT` (JSON web token). These can be read from the responses of `login`, `refresh`, and `me` auth operations.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
You can access the logged-in user from access control functions and hooks from the `req.user` property.
|
||||
<br />
|
||||
[Learn more about token data](/docs/authentication/token-data).
|
||||
</Banner>
|
||||
|
||||
### Identifying Users Via The Authorization Header
|
||||
|
||||
In addition to authenticating via an HTTP-only cookie, you can also identify users via the `Authorization` header on an HTTP request.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
const user = await fetch('http://localhost:3000/api/users/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
email: 'dev@payloadcms.com',
|
||||
password: 'password',
|
||||
})
|
||||
}).then(req => await req.json())
|
||||
|
||||
const request = await fetch('http://localhost:3000', {
|
||||
headers: {
|
||||
Authorization: `JWT ${user.token}`,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Omitting The Token
|
||||
|
||||
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponse` to `true` like so:
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const UsersWithoutJWTs: CollectionConfig = {
|
||||
slug: 'users-without-jwts',
|
||||
auth: {
|
||||
removeTokenFromRepsonse: true, // highlight-line
|
||||
},
|
||||
}
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Authentication Operations
|
||||
label: Operations
|
||||
order: 30
|
||||
order: 80
|
||||
desc: Enabling Authentication automatically makes key operations available such as Login, Logout, Verify, Unlock, Reset Password and more.
|
||||
keywords: authentication, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
@@ -71,106 +71,34 @@ export const Admins: CollectionConfig = {
|
||||
|
||||
Once enabled, each document that is created within the Collection can be thought of as a `user` - who can make use of commonly required authentication functions such as logging in / out, resetting their password, and more.
|
||||
|
||||
## Authentication Strategies
|
||||
|
||||
Out of the box Payload ships with a few powerful authentication strategies. HTTP-Only Cookies, JWT's and API-Keys, they can work together or individually. You can also have multiple collections that have auth enabled, but only 1 of them can be used to log into the admin panel.
|
||||
|
||||
### HTTP-Only Cookies
|
||||
|
||||
HTTP-only cookies are a highly secure method of storing identifiable data on a user's device so that Payload can automatically recognize a returning user until their cookie expires. They are totally protected from common XSS attacks and <strong>cannot be read by JavaScript in the browser</strong>, unlike JWT's.
|
||||
|
||||
You can learn more about this strategy from the [HTTP-Only Cookies](/docs/authentication/http-only-cookies) docs.
|
||||
|
||||
### JSON Web Tokens
|
||||
|
||||
JWT (JSON Web Tokens) can also be utilized to perform authentication. Tokens are generated on `login`, `refresh` and `me` operations and can be attached to future requests to authenticate users.
|
||||
|
||||
You can learn more about this strategy from the [JWT](/docs/authentication/jwt) docs.
|
||||
|
||||
### API Keys
|
||||
|
||||
API Keys can be enabled on auth collections. These are particularly useful when you want to authenticate against Payload from a third party service.
|
||||
|
||||
You can learn more about this strategy from the [API Keys](/docs/authentication/api-keys) docs.
|
||||
|
||||
### Custom Strategies
|
||||
|
||||
There are cases where these may not be enough for your application. Payload is extendable by design so you can wire up your own strategy when you need to.
|
||||
|
||||
You can learn more about custom strategies from the [Custom Strategies](/docs/authentication/custom-strategies) docs.
|
||||
|
||||
## Logging in / out, resetting password, etc.
|
||||
|
||||
[Click here](/docs/authentication/operations) for a list of all automatically-enabled Auth operations, including `login`, `logout`, `refresh`, and others.
|
||||
|
||||
## Token-based auth
|
||||
|
||||
Successfully logging in returns a `JWT` (JSON web token) which is how a user will identify themselves to Payload. By providing this JWT via either an HTTP-only cookie or an `Authorization: JWT` or `Authorization: Bearer` header, Payload will automatically identify the user and add its user JWT data to the `req`, which is available throughout Payload including within access control, hooks, and more.
|
||||
|
||||
You can specify what data gets encoded to the JWT token by setting `saveToJWT` to true in your auth collection fields. If you wish to use a different key other than the field `name`, you can provide it to `saveToJWT` as a string. It is also possible to use `saveToJWT` on fields that are nested in inside groups and tabs. If a group has a `saveToJWT` set it will include the object with all sub-fields in the token. You can set `saveToJWT: false` for any fields you wish to omit. If a field inside a group has `saveToJWT` set, but the group does not, the field will be included at the top level of the token.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
You can access the logged-in user from access control functions and hooks via the{' '}
|
||||
<strong>req</strong>. The logged-in user is automatically added as the <strong>user</strong>{' '}
|
||||
property.
|
||||
</Banner>
|
||||
|
||||
## HTTP-only cookies
|
||||
|
||||
Payload `login`, `logout`, and `refresh` operations make use of HTTP-only cookies for authentication purposes. HTTP-only cookies are a highly secure method of storing identifiable data on a user's device so that Payload can automatically recognize a returning user until their cookie expires. They are totally protected from common XSS attacks and cannot be read at all via JavaScript in the browser.
|
||||
|
||||
#### Automatic browser inclusion
|
||||
|
||||
Modern browsers automatically include `http-only` cookies when making requests directly to URLs—meaning that if you are running your API on http://example.com, and you have logged in and visit http://example.com/test-page, your browser will automatically include the Payload authentication cookie for you.
|
||||
|
||||
#### Using Fetch or other HTTP APIs
|
||||
|
||||
However, if you use `fetch` or similar APIs to retrieve Payload resources from its REST or GraphQL API, you need to specify to include credentials (cookies).
|
||||
|
||||
Fetch example, including credentials:
|
||||
|
||||
```ts
|
||||
const response = await fetch('http://localhost:3000/api/pages', {
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
const pages = await response.json()
|
||||
```
|
||||
|
||||
For more about how to automatically include cookies in requests from your app to your Payload API, [click here](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included).
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
To make sure you have a Payload cookie set properly in your browser after logging in, you can use
|
||||
Chrome's Developer Tools - Application - Cookies - [your-domain-here]. The Chrome Developer tools
|
||||
will still show HTTP-only cookies, even when JavaScript running on the page can't.
|
||||
</Banner>
|
||||
|
||||
## CSRF Protection
|
||||
|
||||
CSRF (cross-site request forgery) attacks are common and dangerous. By using an HTTP-only cookie, Payload removes many XSS vulnerabilities, however, CSRF attacks can still be possible.
|
||||
|
||||
For example, let's say you have a very popular app running at coolsite.com. This app allows users to manage finances and send / receive money. As Payload is using HTTP-only cookies, that means that browsers automatically will include cookies when sending requests to your domain - no matter what page created the request.
|
||||
|
||||
So, if a user of coolsite.com is logged in and just browsing around on the internet, they might stumble onto a page with bad intentions. That bad page might automatically make requests to all sorts of sites to see if they can find one that they can log into - and coolsite.com might be on their list. If your user was logged in while they visited that evil site, the attacker could do whatever they wanted as if they were your coolsite.com user by just sending requests to the coolsite API (which would automatically include the auth cookie). They could send themselves a bunch of money from your user's account, change the user's password, etc. This is what a CSRF attack is.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>
|
||||
To protect against CSRF attacks, Payload only accepts cookie-based authentication from domains
|
||||
that you explicitly whitelist.
|
||||
</strong>
|
||||
</Banner>
|
||||
|
||||
To define domains that should allow users to identify themselves via the Payload HTTP-only cookie, use the `csrf` option on the base Payload config to whitelist domains that you trust.
|
||||
|
||||
`payload.config.ts`:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
const config = buildConfig({
|
||||
collections: [
|
||||
// collections here
|
||||
],
|
||||
// highlight-start
|
||||
csrf: [
|
||||
// whitelist of domains to allow cookie auth from
|
||||
'https://your-frontend-app.com',
|
||||
'https://your-other-frontend-app.com',
|
||||
],
|
||||
// highlight-end
|
||||
})
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
## Identifying users via the Authorization Header
|
||||
|
||||
In addition to authenticating via an HTTP-only cookie, you can also identify users via the `Authorization` header on an HTTP request.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
const request = await fetch('http://localhost:3000', {
|
||||
headers: {
|
||||
Authorization: `JWT ${token}`,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can retrieve a user's token via the response to `login`, `refresh`, and `me` auth operations.
|
||||
|
||||
107
docs/authentication/token-data.mdx
Normal file
107
docs/authentication/token-data.mdx
Normal file
@@ -0,0 +1,107 @@
|
||||
---
|
||||
title: Token Data
|
||||
label: Token Data
|
||||
order: 70
|
||||
desc: Storing data for read on the request object.
|
||||
keywords: authentication, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
During the lifecycle of a request you will be able to access the data you have configured to be stored in the JWT by accessing `req.user`. The user object is automatically appeneded to the request for you.
|
||||
|
||||
### Definining Token Data
|
||||
|
||||
You can specify what data gets encoded to the Cookie/JWT-Token by setting `saveToJWT` property on fields within your auth collection.
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
auth: true,
|
||||
fields: [
|
||||
{
|
||||
// will be stored in the JWT
|
||||
saveToJWT: true,
|
||||
type: 'select',
|
||||
name: 'role',
|
||||
options: [
|
||||
'super-admin',
|
||||
'user',
|
||||
]
|
||||
},
|
||||
{
|
||||
// the entire object will be stored in the JWT
|
||||
// tab fields can do the same thing!
|
||||
saveToJWT: true,
|
||||
type: 'group',
|
||||
name: 'group1',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'includeField',
|
||||
},
|
||||
{
|
||||
// will be omitted from the JWT
|
||||
saveToJWT: false,
|
||||
type: 'text',
|
||||
name: 'omitField',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
name: 'group2',
|
||||
fields: [
|
||||
{
|
||||
// will be stored in the JWT
|
||||
// but stored at the top level
|
||||
saveToJWT: true,
|
||||
type: 'text',
|
||||
name: 'includeField',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'omitField',
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br/>
|
||||
If you wish to use a different key other than the field `name`, you can define `saveToJWT` as a string.
|
||||
</Banner>
|
||||
|
||||
|
||||
### Using Token Data
|
||||
|
||||
This is especially helful when writing hooks and access control that depend on user defined fields.
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Invoices: CollectionConfig = {
|
||||
slug: 'invoices',
|
||||
access: {
|
||||
read: ({ req, data }) => {
|
||||
if (!req?.user) return false
|
||||
// highlight-start
|
||||
if ({ req.user?.role === 'super-admin'}) {
|
||||
return true
|
||||
}
|
||||
// highlight-end
|
||||
return data.owner === req.user.id
|
||||
}
|
||||
}
|
||||
fields: [
|
||||
{
|
||||
name: 'owner',
|
||||
relationTo: 'users'
|
||||
},
|
||||
// ... other fields
|
||||
],
|
||||
}
|
||||
```
|
||||
@@ -45,7 +45,7 @@ Any of the features in Payload Cloud that require environment variables will aut
|
||||
|
||||
<Banner type="warning">
|
||||
Note: For security reasons, any variables you wish to provide to the Admin panel must be prefixed
|
||||
with `PAYLOAD_PUBLIC_`. Learn more
|
||||
with `NEXT_PUBLIC_`. Learn more
|
||||
[here](https://payloadcms.com/docs/admin/environment-vars).
|
||||
</Banner>
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ desc: Structure your Collections for your needs by defining fields, adding slugs
|
||||
keywords: collections, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Payload Collections are defined through configs of their own, and you can define as many as your application needs. Each
|
||||
Collection will scaffold a new collection automatically in your database of choice, based on fields that you define.
|
||||
Payload Collections are defined through configs of their own, and you can define as many as your application needs. Each Collection will scaffold a new collection automatically in your database of choice, based on fields that you define.
|
||||
|
||||
It's often best practice to write your Collections in separate files and then import them into the main Payload config.
|
||||
|
||||
@@ -38,7 +37,7 @@ _\* An asterisk denotes that a property is required._
|
||||
### Simple collection example
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Orders: CollectionConfig = {
|
||||
slug: 'orders',
|
||||
@@ -64,6 +63,8 @@ You can find an assortment
|
||||
of [example collection configs](https://github.com/payloadcms/public-demo/tree/master/src/payload/collections) in the Public
|
||||
Demo source code on GitHub.
|
||||
|
||||
You can also find full, ready-to-go [Ecommerce](https://github.com/payloadcms/payload/tree/main/templates/ecommerce) and [Website](https://github.com/payloadcms/payload/tree/main/templates/website) templates on our GitHub repo.
|
||||
|
||||
## Admin options
|
||||
|
||||
You can customize the way that the Admin panel behaves on a collection-by-collection basis by defining the `admin`
|
||||
@@ -98,12 +99,12 @@ Clicking the Preview button will link to the URL that is generated by the functi
|
||||
**The preview function accepts two arguments:**
|
||||
|
||||
1. The document being edited
|
||||
1. An `options` object, containing `locale` and `token` properties. The `token` is the currently logged-in user's JWT.
|
||||
1. An `options` object, containing `locale`, `req` and `token` properties. The `token` is the currently logged-in user's JWT.
|
||||
|
||||
**Example collection with preview function:**
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
@@ -174,14 +175,14 @@ those three fields plus the ID field.
|
||||
You can import collection types as follows:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
// This is the type used for incoming collection configs.
|
||||
// Only the bare minimum properties are marked as required.
|
||||
```
|
||||
|
||||
```ts
|
||||
import { SanitizedCollectionConfig } from 'payload/types'
|
||||
import { SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
// This is the type used after an incoming collection config is fully sanitized.
|
||||
// Generally, this is only used internally by Payload.
|
||||
|
||||
@@ -33,7 +33,7 @@ _\* An asterisk denotes that a property is required._
|
||||
### Simple Global example
|
||||
|
||||
```ts
|
||||
import { GlobalConfig } from 'payload/types'
|
||||
import { GlobalConfig } from 'payload'
|
||||
|
||||
const Nav: GlobalConfig = {
|
||||
slug: 'nav',
|
||||
@@ -90,7 +90,7 @@ If the function is specified, a Preview button will automatically appear in the
|
||||
**Example global with preview function:**
|
||||
|
||||
```ts
|
||||
import { GlobalConfig } from 'payload/types'
|
||||
import { GlobalConfig } from 'payload'
|
||||
|
||||
export const MyGlobal: GlobalConfig = {
|
||||
slug: 'my-global',
|
||||
@@ -130,7 +130,7 @@ Globals support all field types that Payload has to offer—including simple fie
|
||||
You can import global types as follows:
|
||||
|
||||
```ts
|
||||
import { GlobalConfig } from 'payload/types'
|
||||
import { GlobalConfig } from 'payload'
|
||||
|
||||
// This is the type used for incoming global configs.
|
||||
// Only the bare minimum properties are marked as required.
|
||||
|
||||
@@ -6,7 +6,32 @@ desc: Manage and customize internationalization support in your CMS editor exper
|
||||
keywords: internationalization, i18n, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Not only does Payload support managing localized content, it also has internationalization support so that users in the [Admin Panel](../admin/overview) can work in their preferred language. Payload's i18n comes by default without any additional setup, and you can extend the default settings to customize the language settings to your needs.
|
||||
Not only does Payload support managing localized content, it also has internationalization support so that admin users can work in their preferred language.
|
||||
|
||||
By default, Payload comes with English installed as the language it uses. But, you can import and pass other languages to the Payload config as well. It's best to only support the languages that you need, because that way, the bundled JavaScript is kept to a minimum for your project.
|
||||
|
||||
Here's an example for how to pass additional languages to Payload for use in translating:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload'
|
||||
import { en } from 'payload/i18n/en'
|
||||
import { de } from 'payload/i18n/de'
|
||||
|
||||
export default buildConfig({
|
||||
/**
|
||||
* Payload accepts specific translations to use.
|
||||
* This is completely optional and will default to English if not provided
|
||||
*/
|
||||
i18n: {
|
||||
// Payload will support either English or German,
|
||||
// able to be specified in preferences on a user-by-user basis
|
||||
supportedLanguages: { en, de },
|
||||
},
|
||||
|
||||
// .. the rest of your config
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Configuration Options
|
||||
|
||||
@@ -44,7 +69,7 @@ While Payload's built-in features come translated, you may want to also translat
|
||||
Here is an example of a simple collection supporting both English and Spanish editors:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
import { CollectionConfig } from 'payload'
|
||||
|
||||
export const Articles: CollectionConfig = {
|
||||
slug: 'articles',
|
||||
@@ -96,7 +121,7 @@ After a user logs in, they can change their language selection in the `/account`
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
If there is a language that Payload does not yet support, we accept code
|
||||
[contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
|
||||
[contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md). You can also make and import your own translation files.
|
||||
</Banner>
|
||||
|
||||
## Node
|
||||
@@ -112,7 +137,7 @@ In your Payload config, you can add translations and customize the settings in `
|
||||
**Example Payload config extending i18n:**
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
//...
|
||||
|
||||
@@ -17,7 +17,7 @@ list of all locales that you'd like to support as well as set a few other option
|
||||
**Example Payload config set up for localization:**
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
@@ -34,7 +34,7 @@ export default buildConfig({
|
||||
**Example Payload config set up for localization with full locales objects:**
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
@@ -64,7 +64,7 @@ export default buildConfig({
|
||||
including [internationalization](/docs/configuration/i18n) support):**
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
|
||||
@@ -10,57 +10,59 @@ Payload is a _config-based_, code-first CMS and application framework. The Paylo
|
||||
|
||||
**Also, because the Payload source code is fully written in TypeScript, its configs are strongly typed—meaning that even if you aren't using TypeScript, your IDE (such as VSCode) may still provide helpful information like type-ahead suggestions while you write your config.**
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Important:</strong>
|
||||
<br />
|
||||
The serializable portions of this config are included in the Payload Admin bundle, so make sure you do not embed any sensitive
|
||||
information.
|
||||
</Banner>
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `admin` \* | Base Payload admin configuration. Specify, custom components, control metadata, set the Admin user collection, and [more](/docs/admin/overview#admin-options). Required. |
|
||||
| `editor` \* | Rich Text Editor which will be used by richText fields. Required. |
|
||||
| `db` \* | Database Adapter which will be used by Payload. Read more [here](/docs/database/overview). Required. |
|
||||
| `serverURL` | A string used to define the absolute URL of your app including the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port |
|
||||
| `collections` | An array of all Collections that Payload will manage. To read more about how to define your collection configs, [click here](/docs/configuration/collections). |
|
||||
| `globals` | An array of all Globals that Payload will manage. For more on Globals and their configs, [click here](/docs/configuration/globals). |
|
||||
| `cors` | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
|
||||
| `localization` | Opt-in and control how Payload handles the translation of your content into multiple locales. [More](/docs/configuration/localization) |
|
||||
| `graphQL` | Manage GraphQL-specific functionality here. Define your own queries and mutations, manage query complexity limits, and [more](/docs/graphql/overview#graphql-options). |
|
||||
| `cookiePrefix` | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| `csrf` | A whitelist array of URLs to allow Payload cookies to be accepted from as a form of CSRF protection. [More](/docs/authentication/overview#csrf-protection) |
|
||||
| `defaultDepth` | If a user does not specify `depth` while requesting a resource, this depth will be used. [More](/docs/getting-started/concepts#depth) |
|
||||
| `maxDepth` | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. |
|
||||
| `indexSortableFields` | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| `upload` | Base Payload upload configuration. [More](/docs/upload/overview#payload-wide-upload-options). |
|
||||
| `routes` | Control the routing structure that Payload binds itself to. Specify `admin`, `api`, `graphQL`, and `graphQLPlayground`. |
|
||||
| `email` | Base email settings to allow Payload to generate email such as Forgot Password requests and other requirements. [More](/docs/email/overview#configuration) |
|
||||
| `debug` | Enable to expose more detailed error information. |
|
||||
| `telemetry` | Disable Payload telemetry by passing `false`. [More](/docs/configuration/overview#telemetry) |
|
||||
| `rateLimit` | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks and [more](/docs/production/preventing-abuse#rate-limiting-requests). |
|
||||
| `hooks` | Tap into Payload-wide hooks. [More](/docs/hooks/overview) |
|
||||
| `plugins` | An array of Payload plugins. [More](/docs/plugins/overview) |
|
||||
| `endpoints` | An array of custom API endpoints added to the Payload router. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| `custom` | Extension point for adding custom data (e.g. for plugins) |
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `admin` | Base Payload admin configuration. Specify custom components, control metadata, set the Admin user collection, and [more](/docs/admin/overview#admin-options). |
|
||||
| `bin` | Register custom bin scripts with the `payload` bin function. |
|
||||
| `editor` | Rich Text Editor which will be used by richText fields. |
|
||||
| `db` \* | Database Adapter which will be used by Payload. Read more [here](/docs/database/overview). Required. |
|
||||
| `serverURL` | A string used to define the absolute URL of your app including the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port |
|
||||
| `collections` | An array of all Collections that Payload will manage. To read more about how to define your collection configs, [click here](/docs/configuration/collections). |
|
||||
| `globals` | An array of all Globals that Payload will manage. For more on Globals and their configs, [click here](/docs/configuration/globals). |
|
||||
| `cors` | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
|
||||
| `localization` | Opt-in and control how Payload handles the translation of your content into multiple locales. [More](/docs/configuration/localization) |
|
||||
| `graphQL` | Manage GraphQL-specific functionality here. Define your own queries and mutations, manage query complexity limits, and [more](/docs/graphql/overview#graphql-options). |
|
||||
| `cookiePrefix` | A string that will be prefixed to all cookies that Payload sets. |
|
||||
| `csrf` | A whitelist array of URLs to allow Payload cookies to be accepted from as a form of CSRF protection. [More](/docs/authentication/overview#csrf-protection) |
|
||||
| `defaultDepth` | If a user does not specify `depth` while requesting a resource, this depth will be used. [More](/docs/getting-started/concepts#depth) |
|
||||
| `defaultMaxTextLength` | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
|
||||
| `maxDepth` | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. |
|
||||
| `indexSortableFields` | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
|
||||
| `upload` | Base Payload upload configuration. [More](/docs/upload/overview#payload-wide-upload-options). |
|
||||
| `routes` | Control the routing structure that Payload binds itself to. Specify `admin`, `api`, `graphQL`, and `graphQLPlayground`. |
|
||||
| `email` | Cofigure the email adapter for Payload to use. |
|
||||
| `debug` | Enable to expose more detailed error information. |
|
||||
| `telemetry` | Disable Payload telemetry by passing `false`. [More](/docs/configuration/overview#telemetry) |
|
||||
| `rateLimit` | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks and [more](/docs/production/preventing-abuse#rate-limiting-requests). |
|
||||
| `hooks` | Tap into Payload-wide hooks. [More](/docs/hooks/overview) |
|
||||
| `plugins` | An array of Payload plugins. [More](/docs/plugins/overview) |
|
||||
| `endpoints` | An array of custom API endpoints added to the Payload router. [More](/docs/rest-api/overview#custom-endpoints) |
|
||||
| `custom` | Extension point for adding custom data (e.g. for plugins) |
|
||||
| `i18n` | Internationalization configuration. Pass all i18n languages you'd like the admin UI to support. Defaults to English-only. [More](/docs/beta/configuration/i18n) |
|
||||
| `secret` \* | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. Required. |
|
||||
| `sharp` | If you would like Payload to offer cropping, focal point selection, and automatic media resizing, install and pass the Sharp module to the config here. |
|
||||
| `typescript` | Configure TypeScript settings here. [More](#typescript) |
|
||||
|
||||
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### Simple example
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres' // beta
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical' // beta
|
||||
import { slateEditor } from '@payloadcms/richtext-slate'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export default buildConfig({
|
||||
db: mongooseAdapter({}) // or postgresAdapter({}),
|
||||
editor: lexicalEditor({}) // or slateEditor({})
|
||||
secret: process.env.PAYLOAD_SECRET || '',
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI,
|
||||
}), // or postgresAdapter({}),
|
||||
editor: lexicalEditor({}),
|
||||
collections: [
|
||||
{
|
||||
slug: 'pages',
|
||||
@@ -105,26 +107,25 @@ You can see a full [example config](https://github.com/payloadcms/public-demo/bl
|
||||
|
||||
## Using environment variables in your config
|
||||
|
||||
We suggest using the `dotenv` package to handle environment variables alongside of Payload. All that's necessary to do is to require the package as high up in your application as possible (for example, at the top of your `server.js` file), and ensure that it can find an `.env` file that you create.
|
||||
By default, Next.js will load `.env` files based on your `NODE_ENV`. If you are using Payload outside of Next.js, we suggest using the `dotenv` package to handle environment variables from `.env` files. All that's necessary to do is to require the package as high up in your application as possible (for example, at the top of your `payload.config.ts` file), and ensure that it can find an `.env` file that you create.
|
||||
|
||||
**Add this line to the top of your server:**
|
||||
**If necessary, you can add this code to the top of your Payload config:**
|
||||
|
||||
```ts
|
||||
import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
|
||||
```
|
||||
require('dotenv').config()
|
||||
// ...
|
||||
// the rest of your `server.js` file goes here
|
||||
// the rest of your `payload.config.ts` file goes here
|
||||
```
|
||||
|
||||
Note that if you rely on any environment variables in your config itself, you should also call `dotenv()` at the top of your config itself as well. There's no harm in calling it in both your server and your config itself!
|
||||
|
||||
**Here is an example project structure w/ `dotenv` and an `.env` file:**
|
||||
|
||||
```
|
||||
project-name
|
||||
---- .env
|
||||
---- package.json
|
||||
---- payload.config.js
|
||||
---- server.js
|
||||
---- payload.config.ts
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
@@ -137,7 +138,7 @@ project-name
|
||||
|
||||
## Customizing & Automating Config Location Detection
|
||||
|
||||
Payload is designed to automatically locate your configuration file. By default, it will first look in the root of your current working directory for a file named `payload.config.js` or `payload.config.ts` if you're using TypeScript.
|
||||
For Payload command-line scripts, we need to be able to locate your Payload config. We'll check a variety of locations for the presence of `payload.config.ts` by default, including the root current working directory, your `tsconfig`'s `rootDir`, and your `tsconfig`'s `outDir`.
|
||||
|
||||
In development mode, if the configuration file is not found at the root, Payload will attempt to read your `tsconfig.json`, and search in the directory specified in `compilerOptions.rootDir` (typically "src").
|
||||
|
||||
@@ -154,35 +155,74 @@ In addition to the above automated detection, you can specify your own location
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "PAYLOAD_CONFIG_PATH=path/to/custom-config.js node server.js"
|
||||
"payload": "PAYLOAD_CONFIG_PATH=/path/to/custom-config.ts payload"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When `PAYLOAD_CONFIG_PATH` is set, Payload will use this path to load the configuration, bypassing all automated detection.
|
||||
|
||||
## Developing within the Config
|
||||
|
||||
Payload comes with `isomorphic-fetch` installed which means that even in Node, you can use the `fetch` API just as you would within the browser. No need to import `axios` or similar, unless you want to!
|
||||
|
||||
## TypeScript
|
||||
|
||||
Payload exposes a variety of TypeScript settings that you can leverage on your Config's `typescript` property.
|
||||
|
||||
**`autoGenerate`**
|
||||
|
||||
By default, in Next.js development mode, Payload will auto-generate TypeScript interfaces for all collections and globals that your config defines.
|
||||
|
||||
You can opt out by setting `typescript.autoGenerate: false`.
|
||||
|
||||
**`declare`**
|
||||
|
||||
By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. This promotes strong typing across all of Payload's APIs. However, if you are using your Payload config in a monorepo sub-package, and you are using it in multiple applications, you might want to disable this automatic declaration and then manually add the `declare` block to a file that you control.
|
||||
|
||||
In these cases, you can set `typescript.declare: false` to opt out.
|
||||
|
||||
**`outputFile`**
|
||||
|
||||
You can control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path.
|
||||
|
||||
### Importing Payload config types
|
||||
|
||||
You can import config types as follows:
|
||||
|
||||
```ts
|
||||
import { Config } from 'payload/config'
|
||||
import { Config } from 'payload'
|
||||
|
||||
// This is the type used for an incoming Payload config.
|
||||
// Only the bare minimum properties are marked as required.
|
||||
```
|
||||
|
||||
```ts
|
||||
import { SanitizedConfig } from 'payload/config'
|
||||
import { SanitizedConfig } from 'payload'
|
||||
|
||||
// This is the type used after an incoming Payload config is fully sanitized.
|
||||
// Generally, this is only used internally by Payload.
|
||||
```
|
||||
|
||||
### Server config vs. client config
|
||||
|
||||
Payload's full config is only available on the server, but Payload dynamically reduces your config down to only what is safe to send to the client-side admin panel. All server-only properties are removed as well as all functions (hooks, validations, conditional logic, access control, etc).
|
||||
|
||||
Anywhere within the client-side admin UI, you can access your client-safe config which is typed as `ClientConfig`. The client config is JSON-serializable.
|
||||
|
||||
Here's an example showing how to access your config on the client-side:
|
||||
|
||||
```ts
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useConfig } from '@payloadcms/ui'
|
||||
|
||||
const MyClientComponent: React.FC = () => {
|
||||
// Get access to your config
|
||||
const config = useConfig()
|
||||
|
||||
return (
|
||||
// ..
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Telemetry
|
||||
|
||||
Payload collects **completely anonymous** telemetry data about general usage. This data is super important to us and helps us accurately understand how we're growing and what we can do to build the software into everything that it can possibly be. The telemetry that we collect also help us demonstrate our growth in an accurate manner, which helps us as we seek investment to build and scale our team. If we can accurately demonstrate our growth, we can more effectively continue to support Payload as free and open-source software. To opt out of telemetry, you can pass `telemetry: false` within your Payload config.
|
||||
|
||||
@@ -124,3 +124,90 @@ Drops all entities from the database and re-runs all migrations from scratch.
|
||||
```text
|
||||
npm run payload migrate:fresh
|
||||
```
|
||||
|
||||
## When to run migrations
|
||||
|
||||
Depending on which database adapter you use, your migration workflow might differ subtly.
|
||||
|
||||
In relational databases, migrations will be **required** for non-development database environments. But with MongoDB, you might only need to run migrations once in a while (or never even need them).
|
||||
|
||||
#### MongoDB
|
||||
|
||||
In MongoDB, you'll only ever really need to run migrations for times where you change your database shape, and you have lots of existing data that you'd like to transform from Shape A to Shape B.
|
||||
|
||||
In this case, you can create a migration by running `pnpm payload migrate:create`, and then write the logic that you need to perform to migrate your documents to their new shape. You can then either run your migrations in CI before you build / deploy, or you can run them locally, against your production database, by using your production database connection string on your local computer and running the `pnpm payload migrate` command.
|
||||
|
||||
#### Postgres
|
||||
|
||||
In relational databases like Postgres, migrations are a bit more important, because each time you add a new field or a new collection, you'll need to update the shape of your database to match your Payload config (otherwise you'll see errors upon trying to read / write your data).
|
||||
|
||||
That means that Postgres users of Payload should become familiar with the entire migration workflow from top to bottom.
|
||||
|
||||
Here is an overview of a common workflow for working locally against a development database, creating migrations, and then running migrations against your production database before deploying.
|
||||
|
||||
**1 - work locally using push mode**
|
||||
|
||||
Payload uses Drizzle ORM's powerful `push` mode to automatically sync data changes to your database for you while in development mode. By default, this is enabled and is the suggested workflow to using Postgres and Payload while doing local development.
|
||||
|
||||
You can disable this setting and solely use migrations to manage your local development database (pass `push: false` to your Postgres adapter), but if you do disable it, you may see frequent errors while running development mode. This is because Payload will have updated to your new data shape, but your local database will not have updated.
|
||||
|
||||
For this reason, we suggest that you leave `push` as its default setting and treat your local dev database as a sandbox.
|
||||
|
||||
For more information about push mode and prototyping in development, [click here](/docs/beta/database/postgres#prototyping-in-dev-mode).
|
||||
|
||||
The typical workflow in Payload is to build out your Payload configs, install plugins, and make progress in development mode - allowing Drizzle to push your changes to your local database for you. Once you're finished, you can create a migration.
|
||||
|
||||
But importantly, you do not need to run migrations against your development database, because Drizzle will have already pushed your changes to your database for you.
|
||||
|
||||
<Banner type="warning">
|
||||
Warning: do not mix "push" and migrations with your local development database. If you use "push"
|
||||
locally, and then try to migrate, Payload will throw a warning, telling you that these two methods
|
||||
are not meant to be used interchangeably.
|
||||
</Banner>
|
||||
|
||||
**2 - create a migration**
|
||||
|
||||
Once you're done with working in your Payload config, you can create a migration. It's best practice to try and complete a specific task or fully build out a feature before you create a migration.
|
||||
|
||||
But once you're ready, you can run `pnpm payload migrate:create`, which will perform the following steps for you:
|
||||
|
||||
- We will look for any existing migrations, and automatically generate SQL changes necessary to convert your schema from its prior state to the new state of your Payload config
|
||||
- We will then create a new migration file in your `/migrations` folder that contains all the SQL necessary to be run
|
||||
|
||||
We won't immediately run this migration for you, however.
|
||||
|
||||
<Banner type="success">
|
||||
Tip: migrations created by Payload are relatively programmatic in nature, so there should not be any surprises, but before you check in the created migration it's a good idea to always double-check the contents of the migration files.
|
||||
</Banner>
|
||||
|
||||
**3 - set up your build process to run migrations**
|
||||
|
||||
Generally, you want to run migrations before you build Payload for production. This typically happens in your CI pipeline and can usually be configured on platforms like Payload Cloud, Vercel, or Netlify by specifying your build script.
|
||||
|
||||
A common set of scripts in a `package.json`, set up to run migrations in CI, might look like this:
|
||||
|
||||
```js
|
||||
"scripts": {
|
||||
// For running in dev mode
|
||||
"dev": "next dev --turbo",
|
||||
|
||||
// To build your Next + Payload app for production
|
||||
"build": "next build",
|
||||
|
||||
// A "tie-in" to Payload's CLI for convenience
|
||||
// this helps you run `pnpm payload migrate:create` and similar
|
||||
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
|
||||
|
||||
// This command is what you'd set your `build script` to.
|
||||
// Notice how it runs `payload migrate` and then `pnpm build`?
|
||||
// This will run all migrations for you before building, in your CI,
|
||||
// against your production database
|
||||
"ci": "payload migrate && pnpm build",
|
||||
},
|
||||
```
|
||||
|
||||
In the example above, we've specified a `ci` script which we can use as our "build script" in the platform that we are deploying to production with.
|
||||
|
||||
This will require that your build pipeline can connect to your database, and it will simply run the `payload migrate` command prior to starting the build process. By calling `payload migrate`, Payload will automatically execute any migrations in your `/migrations` folder that have not yet been executed against your production database, in the order that they were created.
|
||||
|
||||
If it fails, the deployment will be rejected. But now, with your build script set up to run your migrations, you will be all set! Next time you deploy, your CI will execute the required migrations for you, and your database will be caught up with the shape that your Payload config requires.
|
||||
|
||||
@@ -30,13 +30,13 @@ export default buildConfig({
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
|
||||
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
|
||||
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
|
||||
| Option | Description |
|
||||
| -------------------- | ----------- |
|
||||
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
|
||||
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
|
||||
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `transactionOptions` | An object with configuration properties used in [transactions](https://www.mongodb.com/docs/manual/core/transactions/) or `false` which will disable the use of transactions. | |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `transactionOptions` | An object with configuration properties used in [transactions](https://www.mongodb.com/docs/manual/core/transactions/) or `false` which will disable the use of transactions. |
|
||||
|
||||
## Access to Mongoose models
|
||||
|
||||
|
||||
@@ -8,11 +8,6 @@ keywords: Postgres, documentation, typescript, Content Management System, cms, h
|
||||
|
||||
To use Payload with Postgres, install the package `@payloadcms/db-postgres`. It leverages Drizzle ORM and `node-postgres` to interact with a Postgres database that you provide.
|
||||
|
||||
<Banner>
|
||||
The Postgres database adapter is currently in beta. If you would like to help us test this
|
||||
package, we'd love to hear if you find any bugs or issues!
|
||||
</Banner>
|
||||
|
||||
It automatically manages changes to your database for you in development mode, and exposes a full suite of migration controls for you to leverage in order to keep other database environments in sync with your schema. DDL transformations are automatically generated.
|
||||
|
||||
To configure Payload to use Postgres, pass the `postgresAdapter` to your Payload config as follows:
|
||||
@@ -81,17 +76,6 @@ Alternatively, you can disable `push` and rely solely on migrations to keep your
|
||||
|
||||
## Migration workflows
|
||||
|
||||
Migrations are extremely powerful thanks to the seamless way that Payload and Drizzle work together. Let's take the following scenario:
|
||||
In Postgres, migrations are a fundamental aspect of working with Payload and you should become familiar with how they work.
|
||||
|
||||
1. You are building your Payload config locally, with a local database used for testing.
|
||||
1. You have left the default setting of `push` enabled, so every time you change your Payload config (add or remove fields, collections, etc.), Drizzle will automatically push changes to your local DB.
|
||||
1. Once you're done with your changes, or have completed a feature, you can run `npm run payload migrate:create`.
|
||||
1. Payload and Drizzle will look for any existing migrations, and automatically generate all SQL changes necessary to convert your schema from its prior state into the state of your current Payload config, and store the resulting DDL in a newly created migration.
|
||||
1. Once you're ready to go to production, you will be able to run `npm run payload migrate` against your production database, which will apply any new migrations that have not yet run.
|
||||
1. Now your production database is in sync with your Payload config!
|
||||
|
||||
<Banner type="warning">
|
||||
Warning: do not mix "push" and migrations with your local development database. If you use "push"
|
||||
locally, and then try to migrate, Payload will throw a warning, telling you that these two methods
|
||||
are not meant to be used interchangeably.
|
||||
</Banner>
|
||||
For more information about migrations, [click here](/docs/beta/database/migrations#when-to-run-migrations).
|
||||
|
||||
@@ -37,4 +37,4 @@ example/
|
||||
├── svelte/
|
||||
```
|
||||
|
||||
...where `payload` is your Payload project, and the other directories are dedicated to their respective front-end framework. We are adding new examples every day, so if your framework of choice is not yet supported in any particular example, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions) or open a new [PR](https://github.com/payloadcms/payload/pulls) to add it yourself.
|
||||
Where `payload` is your Payload project, and the other directories are dedicated to their respective front-end framework. We are adding new examples every day, so if your framework of choice is not yet supported in any particular example, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions) or open a new [PR](https://github.com/payloadcms/payload/pulls) to add it yourself.
|
||||
|
||||
@@ -28,7 +28,10 @@ Right now, Payload is officially supporting two rich text editors:
|
||||
<Banner type="success">
|
||||
<strong>
|
||||
Consistent with Payload's goal of making you learn as little of Payload as possible, customizing
|
||||
and using the Rich Text Editor does not involve learning how to develop for a <em>Payload</em>{' '}
|
||||
and using the Rich Text Editor does not involve learning how to develop for a
|
||||
{' '}
|
||||
<em>Payload</em>
|
||||
{' '}
|
||||
rich text editor.
|
||||
</strong>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Payload is based around a small and intuitive set of concepts. Before starting t
|
||||
|
||||
<Banner type="info">The Payload config is where you configure everything that Payload does.</Banner>
|
||||
|
||||
By default, the Payload config lives in the root folder of your code and is named `payload.config.js` (`payload.config.ts` if you're using TypeScript), but you can customize its name and where you store it. You can write full functions and even full React components right into your config.
|
||||
By default, the Payload config lives in the root folder of your code and is named `payload.config.ts`, but you can customize its name and where you store it. You can write full functions and even full React components right into your config.
|
||||
|
||||
## Collections
|
||||
|
||||
@@ -63,6 +63,83 @@ Access Control is extremely powerful but easy and intuitive to manage. You can e
|
||||
|
||||
For more, visit the [Access Control documentation](/docs/access-control/overview).
|
||||
|
||||
## Retrieving Payload data
|
||||
|
||||
Everything Payload does (create, read, update, delete, login, logout, etc) is exposed to you via three APIs:
|
||||
|
||||
**1 - Payload's Local API**
|
||||
|
||||
By far one of the most powerful aspects of Payload is the fact that it gives you direct-to-database access to your data. It's _extremely_ fast and does not incur any typical REST API / GraphQL / HTTP overhead - you query your database directly in Node.js / TypeScript.
|
||||
|
||||
Everything is strongly typed and it's extremely nice to use. It works anywhere on the server, including custom Next.js route handlers, Payload hooks, Payload access control, and React Server Components.
|
||||
|
||||
Here's a quick example of a React Server Component fetching page data with Payload's Local API:
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import config from '@payload-config'
|
||||
import { getPayloadHMR } from '@payloadcms/next'
|
||||
|
||||
const MyServerComponent: React.FC = () => {
|
||||
// If you're working in Next.js, and you want HMR,
|
||||
// you should get Payload via the `getPayloadHMR` function.
|
||||
const payload = await getPayloadHMR({ config })
|
||||
|
||||
// If you are writing a standalone script and do not need HMR,
|
||||
// you can get Payload via import { getPayload } from 'payload' instead.
|
||||
|
||||
// The `findResult` here will be fully typed as `PaginatedDocs<Page>`,
|
||||
// where you will have the `docs` that are returned as well as
|
||||
// information about how many items are returned / are available in total / etc
|
||||
const findResult = await payload.find({ collection: 'pages' })
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{findResult.docs.map((page) => {
|
||||
// Render whatever here!
|
||||
// The `page` is fully typed as your Pages collection!
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For more docs about the Payload Local API, [click here](/docs/beta/local-api/overview).
|
||||
|
||||
**2 - The REST API**
|
||||
|
||||
By default, the Payload REST API will be mounted automatically for you at the `/api` path of your app.
|
||||
|
||||
For example, if you have a collection with the `slug` as `pages`, you'd fetch pages via `http://localhost:3000/api/pages`.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```ts
|
||||
const pages = await fetch('http://localhost:3000/api/pages', {
|
||||
// Include cookies, like the Payload auth token
|
||||
credentials: 'include',
|
||||
}).then((res) => res.json())
|
||||
```
|
||||
|
||||
For a full list of REST API endpoints, including examples, [click here](/docs/beta/rest-api/overview).
|
||||
|
||||
**3 - GraphQL**
|
||||
|
||||
Payload automatically exposes GraphQL queries and mutations for everything it does.
|
||||
|
||||
By default, you'll find the GraphQL route handler in your `/app/(payload)/api/graphql` folder, which makes the GraphQL endpoint available by default at `http://localhost:3000/api/graphql`.
|
||||
|
||||
You'll also find a full GraphQL Playground which can be accessible via `http://localhost:3000/api/graphql-playground`.
|
||||
|
||||
You can use any GraphQL client with Payload's GraphQL endpoint. Here are a few packages:
|
||||
|
||||
- [`graphql-request`](https://www.npmjs.com/package/graphql-request) - a very lightweight GraphQL client
|
||||
- [`@apollo/client`](https://www.apollographql.com/docs/react/api/core/ApolloClient/) - an industry-standard GraphQL client with lots of nice features
|
||||
|
||||
If you don't use GraphQL, you can delete those files! But if you do, you'll find that GraphQL is a first-class API in Payload. Either way, the overhead of GraphQL is completely constrained to these endpoints, and will not slow down / affect Payload outside of those endpoints themselves.
|
||||
|
||||
For more docs on GraphQL, [click here](/docs/beta/graphql/overview).
|
||||
|
||||
## Depth
|
||||
|
||||
<Banner type="info">
|
||||
@@ -184,3 +261,49 @@ In this case, the field would not be populated, as the current depth (2) has exc
|
||||
When access control on collections prevents relationship fields from populating, the API response
|
||||
will contain the relationship id instead of the full document.
|
||||
</Banner>
|
||||
|
||||
## Package organization
|
||||
|
||||
Payload is abstracted into a set of dedicated packages, and it's a good idea to familiarize yourself with what each one is responsible for.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Note:</strong>
|
||||
<br/>
|
||||
Version numbers of all official Payload packages are kept in sync - and you should always make sure that you use matching versions for all official Payload packages.
|
||||
</Banner>
|
||||
|
||||
`payload`
|
||||
|
||||
The `payload` package is where core business logic for Payload lives. You can think of Payload as an ORM with superpowers - it contains the logic for all Payload "operations" like `find`, `create`, `update`, and `delete`. It executes access control, hooks, validation, and more.
|
||||
|
||||
Payload itself is extremely compact, and can be used in any Node environment. As long as you have `payload` installed and you have access to your Payload config, you can query and mutate your database directly without going through an unnecessary HTTP layer.
|
||||
|
||||
Payload also contains all TypeScript definitions, which can be imported from `payload` directly.
|
||||
|
||||
Here's how to import some common Payload types:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig, Field, GlobalConfig, Config } from 'payload'
|
||||
```
|
||||
|
||||
`@payloadcms/next`
|
||||
|
||||
Whereas Payload itself is responsible for direct database access, and control over Payload business logic, the `@payloadcms/next` package is responsible for the admin panel and the entire HTTP layer (REST, GraphQL) that Payload exposes.
|
||||
|
||||
`@payloadcms/graphql`
|
||||
|
||||
All of Payload's GraphQL functionality is abstracted into a separate package. Payload, its Admin UI, and REST API have absolutely no overlap with GraphQL, and you will incur no performance overhead from GraphQL if you are not using it. However, it's installed within in the `@payloadcms/next` package so you don't have to install it manually. You do, however, need to have GraphQL installed separately in your `package.json` if you are using GraphQL.
|
||||
|
||||
`@payloadcms/ui`
|
||||
|
||||
This is the UI library that Payload's admin panel uses. All components are exported from this package and can be re-used as you build extensions to the Payload admin UI, or want to use Payload components in your own React apps. Some exports are server components and some are client components.
|
||||
|
||||
`@payloadcms/db-postgres`, `@payloadcms/db-mongodb`
|
||||
|
||||
You can choose which database adapter you'd like to use for your project, and no matter which you choose, the entire data layer for Payload is contained within these packages. You can only use one at a time for any given project.
|
||||
|
||||
`@payloadcms/richtext-lexical`, `@payloadcms/richtext-slate`
|
||||
|
||||
Payload's Rich Text functionality is abstracted into separate packages and if you want to enable Rich Text in your project, you'll need to install one of these packages. We recommend Lexical for all new projects, and this is where Payload will focus its efforts on from this point, but Slate is still supported if you have already built with it.
|
||||
|
||||
Rich Text is entirely optional and you may not need it for your project.
|
||||
|
||||
@@ -10,8 +10,8 @@ keywords: documentation, getting started, guide, Content Management System, cms,
|
||||
|
||||
Payload requires the following software:
|
||||
|
||||
- Any JavaScript package manager (Yarn, NPM, or pnpm)
|
||||
- Node.js version 18+
|
||||
- Any JavaScript package manager (Yarn, NPM, or pnpm - pnpm is preferred)
|
||||
- Node.js version 20.9.0+
|
||||
- Any [compatible database](/docs/database/overview) (MongoDB or Postgres)
|
||||
|
||||
<Banner type="warning">
|
||||
@@ -23,131 +23,151 @@ Payload requires the following software:
|
||||
To quickly scaffold a new Payload app in the fastest way possible, you can use [create-payload-app](https://npmjs.com/package/create-payload-app). To do so, run the following command:
|
||||
|
||||
```
|
||||
npx create-payload-app@latest
|
||||
npx create-payload-app@beta
|
||||
```
|
||||
|
||||
Then just follow the prompts! You'll get set up with a new folder and a functioning Payload app inside.
|
||||
|
||||
## Adding to an existing app
|
||||
|
||||
Adding Payload to either a new or existing TypeScript + Next.js app is super straightforward. To add to an existing app, just run `npm install --save --legacy-peer-deps payload`.
|
||||
Adding Payload to an existing Next.js app is super straightforward. You can either run the `npx create-payload-app@beta` command inside your Next.js project's folder, or manually install Payload by following the steps below.
|
||||
|
||||
From there, the first step is writing a baseline config. Create a new `payload.config.ts` in your project's `/src` directory (or whatever your root TS dir is). The simplest config contains the following:
|
||||
If you don't have a Next.js app already, but you still want to start a project from a blank Next.js app, you can create a new Next.js app using `npx create-next-app` - and then just follow the steps below to install Payload.
|
||||
|
||||
```js
|
||||
import { buildConfig } from 'payload/config'
|
||||
#### 1 - install the relevant packages
|
||||
|
||||
export default buildConfig({
|
||||
// By default, Payload will boot up normally
|
||||
// and you will be provided with a base `User` collection.
|
||||
// But, here is where you define how you'd like Payload to work!
|
||||
})
|
||||
First, you'll want to add the required Payload packages to your project and can do so by running the command below (swap out `pnpm` for your package manager). Note that if you are using NPM, you might need to install using legacy peer deps (`npm i --legacy-peer-deps`).
|
||||
|
||||
```
|
||||
pnpm i payload@beta @payloadcms/next@beta @payloadcms/richtext-lexical@beta sharp graphql
|
||||
```
|
||||
|
||||
Write the above code into your newly created config file. This baseline config will automatically provide you with a default `User` collection. For more information about users and authentication, including how to provide your own user config, jump to the [Authentication](/docs/authentication/config) section.
|
||||
You'll also need to install the database adapter that you'd like to use. You have the option of Postgres (`@payloadcms/db-postgres`) or MongoDB (`@payloadcms/db-mongodb`).
|
||||
|
||||
#### 2 - copy Payload files into your Next.js app folder
|
||||
|
||||
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run.
|
||||
|
||||
The files that Payload needs to have in your `/app` folder do not regenerate, and will never change. Once you slot them in, you never have to revisit them. They are not meant to be edited and simply import Payload dependencies from `@payloadcms/next` for the REST / GraphQL API and Payload admin UI.
|
||||
|
||||
You can copy the Payload `/app` folder files from the Payload blank template on GitHub:
|
||||
|
||||
```
|
||||
https://github.com/payloadcms/payload/tree/beta/templates/blank-3.0/src/app/(payload)
|
||||
```
|
||||
|
||||
Notice how the Payload files are all kept within the `(payload)` [Route Group](https://nextjs.org/docs/app/building-your-application/routing/route-groups)? That's so that the Payload admin UI can have its own separate [Root Layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required) which will allow it to not interfere whatsoever with an existing Next.js app if you have one.
|
||||
|
||||
<Banner type="warning">
|
||||
You may need to copy all of your existing frontend files, including your existing root layout, into its own newly created route group.
|
||||
</Banner>
|
||||
|
||||
Once you have the required Payload files in place in your `/app` folder, you should have something like this:
|
||||
|
||||
```
|
||||
/app
|
||||
-- (payload) // This is the Payload route group
|
||||
-- layout.tsx // Payload's root layout
|
||||
-- admin // The Payload admin UI
|
||||
-- api // Payload's REST / GraphQL handlers
|
||||
-- custom.scss // Define custom SCSS for the Payload admin here
|
||||
-- (my-app) // this is where your existing app goes
|
||||
-- layout.tsx // move your existing root layout here
|
||||
-- page.tsx // your existing home page
|
||||
-- ...etc // whatever else you have
|
||||
```
|
||||
|
||||
You can name the `(my-app)` folder anything you want. The name does not matter and will just be used to clarify your directory structure for yourself. Common names might be `(frontend)`, `(app)`, or similar. [Check out the Admin documentation](/docs/beta/admin/overview) for more information.
|
||||
|
||||
#### 3 - add the Payload plugin to your Next.js config
|
||||
|
||||
Payload has a plugin that it uses to ensure Next.js compatibility with some of the packages Payload relies on, like `mongodb` or `drizzle-kit`.
|
||||
|
||||
You need to import the `withPayload` plugin in your `next.config.js` file and wrap your config with it.
|
||||
|
||||
Here's an example of what your Next.js config should look like after you've imported the Payload plugin:
|
||||
|
||||
```js
|
||||
import { withPayload } from '@payloadcms/next/withPayload'
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Your Next.js config here
|
||||
experimental: {
|
||||
reactCompiler: false
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure you wrap your `nextConfig`
|
||||
// with the `withPayload` plugin
|
||||
export default withPayload(nextConfig)
|
||||
```
|
||||
|
||||
**Important:** Payload is a fully ESM project, and that means the `withPayload` function is an EcmaScript module.
|
||||
|
||||
To import it, you need to make sure your `next.config` file is set up to use ESM.
|
||||
|
||||
You can do this in one of two ways:
|
||||
|
||||
1. Set your own project to use ESM, by adding `"type": "module"` to your `package.json` file
|
||||
2. Give your Next.js config the `.mjs` file extension
|
||||
|
||||
In either case, all `require`s and `export`s in your `next.config` file will need to be converted to `import` / `export` if they are not set up that way already.
|
||||
|
||||
#### 4 - create a Payload config and add it to your TypeScript config
|
||||
|
||||
Finally, you need to create a barebones Payload config.
|
||||
|
||||
Generally the Payload config is located at the root of your repository, or next to your `/app` folder, and is named `payload.config.ts`. Here's what Payload needs at a bare minimum:
|
||||
|
||||
```ts
|
||||
import sharp from 'sharp'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
export default buildConfig({
|
||||
// If you'd like to use Rich Text, pass your editor here
|
||||
editor: lexicalEditor(),
|
||||
|
||||
// Define and configure your collections in this array
|
||||
collections: [],
|
||||
|
||||
// Your Payload secret - should be a complex and secure string, unguessable
|
||||
secret: process.env.PAYLOAD_SECRET || '',
|
||||
|
||||
// Whichever database adapter you're using should go here
|
||||
// Mongoose is shown as an example, but you can also use Postgres
|
||||
db: mongooseAdapter({
|
||||
url: process.env.DATABASE_URI || '',
|
||||
}),
|
||||
|
||||
// If you want to resize images, crop, set focal point, etc.
|
||||
// make sure to install it and pass it to the config.
|
||||
// This is optional - if you don't need to do these things,
|
||||
// you don't need it!
|
||||
sharp,
|
||||
})
|
||||
```
|
||||
|
||||
Although this is just the bare minimum config, there are _many_ more options that you can control here. To reference the full config and all of its options, [click here](/docs/configuration/overview).
|
||||
|
||||
## Server
|
||||
Once you have your config created, update your `tsconfig` to include a `path` that points to your config:
|
||||
|
||||
Now that you've got a baseline Payload config, it's time to initialize Payload. It requires an Express server that you provide, so if you're not familiar with how to set up a baseline Express server, please read up on exactly what Express is and why to use it. Express' own [Documentation](https://expressjs.com/en/starter/hello-world.html) is a good place to start. Otherwise, follow along below for how to build your own Express server to use with Payload.
|
||||
|
||||
1. Run `npm install --save --legacy-peer-deps express` if you have not done so already
|
||||
1. Create a new `server.ts` file in the root directory of your app
|
||||
1. Add the following code to `server.ts`:
|
||||
|
||||
```ts
|
||||
import express from 'express'
|
||||
|
||||
const app = express()
|
||||
|
||||
app.listen(3000, async () => {
|
||||
console.log('Express is now listening for incoming connections on port 3000.')
|
||||
})
|
||||
```
|
||||
|
||||
This server doesn't do anything just yet. But, after you have this in place, we can initialize Payload via its asynchronous `init()` method, which accepts a small set of arguments to tell it how to operate.
|
||||
|
||||
To initialize Payload, update your `server.ts` file to reflect the following code:
|
||||
|
||||
```ts
|
||||
import express from 'express'
|
||||
import payload from 'payload'
|
||||
|
||||
require('dotenv').config()
|
||||
const app = express()
|
||||
|
||||
const start = async () => {
|
||||
await payload.init({
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
express: app,
|
||||
})
|
||||
|
||||
app.listen(3000, async () => {
|
||||
console.log('Express is now listening for incoming connections on port 3000.')
|
||||
})
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@payload-config": [
|
||||
"./payload.config.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
start()
|
||||
```
|
||||
|
||||
A quick reminder: in this configuration, we're making use of environmental variables, `process.env.PAYLOAD_SECRET`. Often, it's smart to store these values in an `.env` file at the root of your directory and set different values for each of your environments (local, stage, prod, etc). The `dotenv` package is very handy and works well alongside of Payload. A typical `.env` file will look like this:
|
||||
#### 5 - fire it up!
|
||||
|
||||
```
|
||||
DATABASE_URI=mongodb://127.0.0.1/your-payload-app
|
||||
PAYLOAD_SECRET=your-payload-secret
|
||||
```
|
||||
|
||||
Here is a list of all properties available to pass through `payload.init`:
|
||||
|
||||
#### secret
|
||||
|
||||
**Required**. This is a secure string that will be used to authenticate with Payload. It can be random but should be at least 14 characters and be very difficult to guess.
|
||||
|
||||
Payload uses this secret key to generate secure user tokens (JWT). Behind the scenes, we do not use your secret key to encrypt directly - instead, we first take the secret key and create an encrypted string using the SHA-256 hash function. Then, we reduce the encrypted string to its first 32 characters. This final value is what Payload uses for encryption.
|
||||
|
||||
#### config
|
||||
|
||||
Allows you to pass your config directly to the onInit function. The config passed here should match the payload.config file.
|
||||
|
||||
#### disableOnInit
|
||||
|
||||
A boolean that disables running your `onInit` function when Payload starts up.
|
||||
|
||||
#### disableDBConnect
|
||||
|
||||
A boolean that disables the database connection when Payload starts up.
|
||||
|
||||
#### email
|
||||
|
||||
An object used to configure SMTP. [Read more](/docs/email/overview).
|
||||
|
||||
#### express
|
||||
|
||||
This is your Express app as shown above. Payload will tie into your existing `app` and scope all of its functionalities to sub-routers. By default, Payload will add an `/admin` router and an `/api` router, but you can customize these paths.
|
||||
|
||||
#### local
|
||||
|
||||
A boolean that when set to `true` tells Payload to start in local-only mode which will bypass setting up API routes. When set to `true`, `express` is not required. This is useful when running scripts that need to use Payload's [local-api](/docs/local-api/overview).
|
||||
|
||||
#### loggerDestination
|
||||
|
||||
Specify destination stream for the built-in Pino logger that Payload uses for internal logging. See [Pino Docs](https://getpino.io/#/docs/api?id=pino-destination) for more info on what is available.
|
||||
|
||||
#### loggerOptions
|
||||
|
||||
Specify options for the built-in Pino logger that Payload uses for internal logging. See [Pino Docs](https://getpino.io/#/docs/api?id=options) for more info on what is available.
|
||||
|
||||
#### onInit
|
||||
|
||||
A function that is called immediately following startup that receives the Payload instance as it's only argument.
|
||||
|
||||
## Test it out
|
||||
|
||||
After you've gotten this far, it's time to boot up Payload. Start your project in your application's folder to get going.
|
||||
After you've gotten this far, it's time to boot up Payload. Start your project in your application's folder to get going. By default, the Next.js dev script is `pnpm dev` (or `npm run dev` if using NPM).
|
||||
|
||||
After it starts, you can go to `http://localhost:3000/admin` to create your first Payload user!
|
||||
|
||||
## Docker
|
||||
|
||||
Looking to deploy Payload with Docker? New projects with `create-payload-app` come with a Dockerfile and docker-compose.yml file ready to go. Examples of these files can be seen in our [Deployment docs](/docs/production/deployment#docker).
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
title: What is Payload?
|
||||
label: What is Payload?
|
||||
order: 10
|
||||
desc: Payload is a next-gen headless Content Management System (CMS) and application framework.
|
||||
keywords: documentation, getting started, guide, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
desc: Payload is a next-gen application framework that can be used as a Content Management System, enterprise tool framework, headless commerce platform, or digital asset management tool.
|
||||
keywords: documentation, getting started, guide, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
<YouTube
|
||||
@@ -11,56 +11,117 @@ keywords: documentation, getting started, guide, Content Management System, cms,
|
||||
title="Payload Introduction - Closing the Gap Between Headless CMS and Application Frameworks"
|
||||
/>
|
||||
|
||||
Payload is a headless CMS and application framework. It's meant to provide a massive boost to your
|
||||
development process, but importantly, stay out of your way as your apps get more complex.
|
||||
**Payload is the Next.js fullstack framework.** Write a Payload config and instantly get:
|
||||
|
||||
- A full admin panel using React server / client components, matching the shape of your data and completely extensible with your own React components
|
||||
- Automatic database schema, including direct DB access and ownership, with migrations, transactions, proper indexing, and more
|
||||
- Instant REST, GraphQL, and straight-to-DB Node APIs
|
||||
- Authentication which can be used in your own apps
|
||||
- A deeply customizable access control pattern
|
||||
- File storage and image management tools like cropping / focal point selection
|
||||
- Live preview - see your frontend render content changes in realtime as you update
|
||||
- Lots more
|
||||
|
||||
### Instant backend superpowers
|
||||
|
||||
No matter what you're building, Payload will give you backend superpowers. It can be installed in one line into any existing Next.js app, and is designed to catapult your development process. Payload takes the most complex and time-consuming parts of any modern web app and makes them simple.
|
||||
|
||||
### Open source - deploy anywhere, including Vercel
|
||||
|
||||
It's fully open source with an MIT license and you can self-host anywhere that you can run a Node app. You can also deploy serverless to hosts like Vercel, right inside your existing Next.js app folder.
|
||||
|
||||
### Fully extensible
|
||||
|
||||
Even in spite of how much you get out of the box, you still have full control over every aspect of your app - be it database, admin UI, or anything else. Every part of Payload has been designed to be extensible and customizable with modern TypeScript / React. And you'll fully understand the code that you write.
|
||||
|
||||
In Payload, there are no "click ops" - as in clicking around in an admin panel to define your schema. In Payload, everything is done the right way—code-first and version controlled like a proper backend. But once developers define how Payload should work, non-technical users can independently make use of its admin panel to manage whatever they need to without having to know code whatsoever.
|
||||
|
||||
## Use cases
|
||||
|
||||
Payload started as a headless Content Management System (CMS), but since, we've seen our community leverage Payload in ways far outside of simply managing pages and blog posts. It's grown into a full-stack TypeScript app framework.
|
||||
|
||||
Large enterprises use Payload to power significant internal tools, retailers power their entire storefronts without the need for headless Shopify, and massive amounts of digital assets are stored + managed within Payload. Of course, websites large and small still use Payload for content management as well.
|
||||
|
||||
### As a CMS
|
||||
|
||||
The biggest barrier in large web projects cited by marketers is engineering. On the flip side, engineers say the opposite. This is a big problem that has yet to be solved even though we have countless CMS options.
|
||||
|
||||
Payload has restored a little love back into the dev / marketer equation with features like Live Preview, redirects, form builders, visual editing, static A/B testing, and more. But even with all this focus on marketing efficiency, we aren't compromising on the developer experience. That way engineers and marketers alike can be proud of the products they build.
|
||||
|
||||
If you're building a website and your frontend is on Next.js, then Payload is a no-brainer.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Payload 2.0 has been released!</strong>
|
||||
<br />
|
||||
Includes Postgres support, Live Preview, Lexical Editor, and more.{' '}
|
||||
<a href="/blog/payload-2-0">Read the announcement</a>.
|
||||
Instead of going out and signing up for a SaaS vendor that makes it so you have to manage two completely separate concerns, with little to no native connection back and forth, just install Payload in your existing Next.js repo and instantly get a full CMS.
|
||||
</Banner>
|
||||
|
||||
Out of the box, Payload gives you a lot of the things that you often need when developing a new website, web app, or native app:
|
||||
Get started with Payload as a CMS using our official Website template:
|
||||
|
||||
- A database to store your data (Postgres and MongoDB supported)
|
||||
- A way to store, retrieve, and manipulate data of any shape via full REST and GraphQL APIs
|
||||
- Authentication—complete with commonly required functionality like registration, email verification, login, & password reset
|
||||
- Deep access control to your data, based on document or field-level functions
|
||||
- File storage and access control
|
||||
- A beautiful admin UI that's generated specifically to suit your data
|
||||
```
|
||||
npx create-payload-app@latest -t website
|
||||
```
|
||||
|
||||
## What does "headless" mean?
|
||||
### For enterprise tools
|
||||
|
||||
A headless CMS is a system that sticks to what it's good at—managing content. It concentrates solely on granting administrators an effective way to author and maintain content, but doesn't control how and where that content is used.
|
||||
When a large organization starts up a new software initiative, there's a lot of plumbing to take care of.
|
||||
|
||||
In this way, the CMS can ensure that its content editing experience is highly polished and effective while avoiding placing creative constraints on designers or restricting development teams. In contrast, traditional content management systems bind the presentation of your content to the storage of your content and severely limit the creativity, development and usability of the content that they manage.
|
||||
- Scaffold the data layer with an ORM or an app framework like Ruby on Rails or Laravel
|
||||
- Implement their SSO provider for authentication
|
||||
- Design an access control pattern for authorization
|
||||
- Open up any REST endpoints required or implement GraphQL queries / mutations
|
||||
- Implement a migrations workflow for the database as it changes over time
|
||||
- Integrate with other third party solutions by crafting a system of webhooks or similar
|
||||
|
||||
At this point this concept is [widely](https://en.wikipedia.org/wiki/Headless_content_management_system) [discussed](https://css-tricks.com/what-is-a-headless-cms/) online, and for good reason. The web has become more complicated and with complexity comes the demand for developers to better structure their code. The rise of interface libraries like React and Vue are now the de-facto standard for building modern applications and traditional content management systems are often not designed to make use of them.
|
||||
And then there's the admin panel. Most enterprise tools require an admin UI, and building one from scratch can be the most time-consuming aspect of any new enterprise tool. There are off-the-shelf packages for app frameworks like Rails, but often the customization is so involved that using Material UI or similar from scratch might be better.
|
||||
|
||||
## Why Payload?
|
||||
Then there are no-code admin builders that could be used. However, wiring up access control and the connection to the data layer, with proper version control, makes this a challenging task as well.
|
||||
|
||||
The team behind Payload has been building websites and apps with existing content management systems and application frameworks for over a decade. We know what works and what doesn't about each of the existing solutions, and to this day have found no silver bullet solution.
|
||||
That's where Payload comes in. Payload instantly provides all of this out of the box, making complex internal tools extremely simple to both spin up and maintain over time. The only custom code that will need to be written is any custom business logic. That means Payload can expedite timelines, keep budgets low, and allow engineers to focus on their specific requirements rather than complex backend / admin UI plumbing.
|
||||
|
||||
**We believe that a CMS should be:**
|
||||
Generally, the best place to start for a new enterprise tool is with a blank canvas, where you can define your own functionality:
|
||||
|
||||
- Cost-effective and should save time and effort
|
||||
- Intuitive for developers and content authors alike
|
||||
- Self-hosted however and wherever the application specifies
|
||||
- Designed in code but used with no coding experience
|
||||
- Blazing fast
|
||||
- Secure
|
||||
- Fully flexible and extensible
|
||||
```
|
||||
npx create-payload-app@latest -t blank
|
||||
```
|
||||
|
||||
Payload is our silver bullet solution. We've blended the best parts of our experience with other CMS and app frameworks into Payload, and we finally have everything we need when we build new apps and websites:
|
||||
### Headless commerce
|
||||
|
||||
- A beautiful, dynamic, customizable admin UI
|
||||
- Extensible and reusable authentication
|
||||
- Content localization
|
||||
- Local file storage
|
||||
- Extremely flexible access control
|
||||
- Field conditional logic
|
||||
- Block-based layout building
|
||||
- Array field type(s)
|
||||
- Security
|
||||
- and much more
|
||||
Companies who prioritize UX generally run into frontend constraints with traditional commerce vendors. These companies will then opt for frontend frameworks like Next.js which allow them to fine-tune their user experience as much as possible—promoting conversions, personalizing experiences, and optimizing for SEO.
|
||||
|
||||
But the challenge with using something like Next.js for headless commerce is that in order for non-technical users to manage the storefront, you instantly need to pair a headless commerce product with a headless CMS. Then, your editors need to bounce back and forth between different admin UIs for different functionality. The code required to seamlessly glue them together on the frontend becomes overly complex.
|
||||
|
||||
Payload can integrate with any payment processor like Stripe and its content authoring capabilities allow it to manage every aspect of a storefront—all in one place.
|
||||
|
||||
If you can build your storefront with a single backend, and only offload things like payment processing, the code will be simpler and the editing experience will be significantly streamlined. Manage products, catalogs, page content, media, and more—all in one spot.
|
||||
|
||||
Payload's official Ecommerce template gives you everything you need for a storefront out of the box, including a Next.js frontend, product variations, and a full Stripe implementation:
|
||||
|
||||
```
|
||||
npx create-payload-app@latest -t ecommerce
|
||||
```
|
||||
|
||||
### Digital asset management
|
||||
|
||||
Payload's API-first tagging, sorting, and querying engine lends itself perfectly to all types of content that a CMS might ordinarily store, but these strong fundamentals also make it a formidable Digital Asset Management (DAM) tool as well.
|
||||
|
||||
Similarly to the Ecommerce use case above, if an organization uses a CMS for its content but a separate DAM for its digital assets, administrators of both tools will need to juggle completely different services for tasks that are closely related. Two subscriptions will need to be managed, two sets of infrastructure will need to be provisioned, and two admin UIs need to be used / learned.
|
||||
|
||||
Payload flattens CMS and DAM into a single tool that makes no compromises on either side. Powerful features like folder-based organization, file versioning, bulk upload, and media access control allow Payload to simultaneously function as a full Digital Asset Management platform as well as a Content Management System at the same time.
|
||||
|
||||
[Click here](https://payloadcms.com/use-cases/digital-asset-management) for more information on how to get started with Payload as a DAM.
|
||||
|
||||
### When Payload might be for you
|
||||
|
||||
- If data ownership and privacy are important to you, and you don't want to allow another proprietary SaaS vendor to host and own your data
|
||||
- If you're building a Next.js site that needs a CMS
|
||||
- If you need to re-use your data outside of a SaaS API
|
||||
- If what you're building has custom business logic requirements outside of a typical headless CMS
|
||||
- You want to deploy serverless on platforms like Vercel
|
||||
|
||||
### When Payload might not be for you
|
||||
|
||||
- If you can manage your project fully with code, and don't need an admin UI
|
||||
- If you are building a website that fits within the limits a tool like Webflow or Framer
|
||||
- If you already have a full database and just need to visualize the data somehow
|
||||
- If you are confident that you won't need code / data ownership at any point in the future
|
||||
|
||||
Ready to get started? First, let's review some high-level concepts that are used in Payload.
|
||||
|
||||
@@ -34,7 +34,7 @@ Both `graphQL.queries` and `graphQL.mutations` functions should return an object
|
||||
`payload.config.js`:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
import myCustomQueryResolver from './graphQL/resolvers/myCustomQueryResolver'
|
||||
|
||||
export default buildConfig({
|
||||
@@ -100,9 +100,24 @@ Contextual information about the currently running GraphQL operation. You can ge
|
||||
|
||||
We've exposed a few types and utilities to help you extend the API further. Payload uses the GraphQL.js package for which you can view the full list of available types in the [official documentation](https://graphql.org/graphql-js/type/).
|
||||
|
||||
**`GraphQLJSON`** & **`GraphQLJSONObject`**
|
||||
|
||||
```ts
|
||||
import { GraphQLJSON, GraphQLJSONObject } from '@payloadcms/graphql/types'
|
||||
```
|
||||
|
||||
**`GraphQL`**
|
||||
|
||||
You can directly import the GraphQL package used by Payload, most useful for typing. For queries, mutations and handlers make sure you use the `GraphQL` and `payload` instances provided via arguments.
|
||||
You can directly import the GraphQL package used by Payload, most useful for typing.
|
||||
|
||||
```ts
|
||||
import { GraphQL } from '@payloadcms/graphql/types'
|
||||
```
|
||||
|
||||
<Banner type="warning">
|
||||
For queries, mutations and handlers make sure you use the `GraphQL` and `payload` instances provided via arguments.
|
||||
</Banner>
|
||||
|
||||
|
||||
**`buildPaginatedListType`**
|
||||
|
||||
@@ -112,6 +127,8 @@ It takes in two arguments, the first for the name of this new schema type and th
|
||||
Example
|
||||
|
||||
```ts
|
||||
import { buildPaginatedListType } from '@payloadcms/graphql/types'
|
||||
|
||||
export const getMyPosts = (GraphQL, payload) => {
|
||||
return {
|
||||
args: {},
|
||||
|
||||
@@ -6,14 +6,20 @@ desc: Output your own GraphQL schema based on your collections and globals to a
|
||||
keywords: headless cms, typescript, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
When working with GraphQL it is useful to have the schema for development of other projects that need to call on your GraphQL endpoint. In Payload the schema is controlled by your collections and globals and is made available to the developer or third parties, it is not necessary for developers using Payload to write schema types manually.
|
||||
In Payload the schema is controlled by your collections and globals. All you need to do is run the generate command and the entire schema will be created for you.
|
||||
|
||||
## Schema generation script
|
||||
|
||||
Run the following command in a Payload project to generate your project's GraphQL schema from Payload:
|
||||
Install `@payloadcms/graphql` as a dev dependency:
|
||||
|
||||
```bash
|
||||
pnpm add @payloadcms/graphql --save-dev
|
||||
```
|
||||
payload generate:graphQLSchema
|
||||
|
||||
Run the following command to generate the schema:
|
||||
|
||||
```bash
|
||||
pnpm payload-graphql generate:schema
|
||||
```
|
||||
|
||||
## Custom Field Schemas
|
||||
@@ -24,7 +30,7 @@ For `array`, `block`, `group` and named `tab` fields, you can generate top level
|
||||
{
|
||||
type: 'group',
|
||||
name: 'meta',
|
||||
interfaceName: 'SharedMeta', <-- here!!
|
||||
interfaceName: 'SharedMeta', // highlight-line
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -41,13 +47,13 @@ For `array`, `block`, `group` and named `tab` fields, you can generate top level
|
||||
will generate:
|
||||
|
||||
```ts
|
||||
// a top level reusable type!!
|
||||
// A top level reusable type will be generated
|
||||
type SharedMeta {
|
||||
title: String
|
||||
description: String
|
||||
}
|
||||
|
||||
// example usage inside collection schema
|
||||
// And will be referenced inside the generated schema
|
||||
type Collection1 {
|
||||
// ...other fields
|
||||
meta: SharedMeta
|
||||
@@ -64,16 +70,18 @@ The above example outputs all your definitions to a file relative from your payl
|
||||
Payload needs to be able to find your config to generate your GraphQL schema.
|
||||
</Banner>
|
||||
|
||||
Payload will automatically try and locate your config, but might not always be able to find it. For example, if you are working in a `/src` directory or similar, you need to tell Payload where to find your config manually by using an environment variable. If this applies to you, you can create an NPM script to make generating your types easier.
|
||||
Payload will automatically try and locate your config, but might not always be able to find it. For example, if you are working in a `/src` directory or similar, you need to tell Payload where to find your config manually by using an environment variable.
|
||||
|
||||
To add an NPM script to generate your types and show Payload where to find your config, open your `package.json` and update the `scripts` property to the following:
|
||||
If this applies to you, create an NPM script to make generating types easier:
|
||||
|
||||
```json
|
||||
// package.json
|
||||
|
||||
{
|
||||
"scripts": {
|
||||
"generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema"
|
||||
"generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload-graphql generate:schema"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you can run `yarn generate:graphQLSchema` to easily generate your schema.
|
||||
Now you can run `pnpm generate:graphQLSchema` to easily generate your schema.
|
||||
|
||||
@@ -29,7 +29,7 @@ At the top of your Payload config you can define all the options to manage Graph
|
||||
Everything that can be done to a Collection via the REST or Local API can be done with GraphQL (outside of uploading files, which is REST-only). If you have a collection as follows:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const PublicUser: CollectionConfig = {
|
||||
slug: 'public-users',
|
||||
@@ -44,32 +44,32 @@ export const PublicUser: CollectionConfig = {
|
||||
|
||||
| Query Name | Operation |
|
||||
| ------------------ | ------------------- |
|
||||
| **`PublicUser`** | `findByID` |
|
||||
| **`PublicUsers`** | `find` |
|
||||
| **`countPublicUsers`** | `count` |
|
||||
| **`mePublicUser`** | `me` auth operation |
|
||||
| `PublicUser` | `findByID` |
|
||||
| `PublicUsers` | `find` |
|
||||
| `countPublicUsers` | `count` |
|
||||
| `mePublicUser` | `me` auth operation |
|
||||
|
||||
**And the following mutations:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ------------------------------ | ------------------------------- |
|
||||
| **`createPublicUser`** | `create` |
|
||||
| **`updatePublicUser`** | `update` |
|
||||
| **`deletePublicUser`** | `delete` |
|
||||
| **`forgotPasswordPublicUser`** | `forgotPassword` auth operation |
|
||||
| **`resetPasswordPublicUser`** | `resetPassword` auth operation |
|
||||
| **`unlockPublicUser`** | `unlock` auth operation |
|
||||
| **`verifyPublicUser`** | `verify` auth operation |
|
||||
| **`loginPublicUser`** | `login` auth operation |
|
||||
| **`logoutPublicUser`** | `logout` auth operation |
|
||||
| **`refreshTokenPublicUser`** | `refresh` auth operation |
|
||||
| `createPublicUser` | `create` |
|
||||
| `updatePublicUser` | `update` |
|
||||
| `deletePublicUser` | `delete` |
|
||||
| `forgotPasswordPublicUser` | `forgotPassword` auth operation |
|
||||
| `resetPasswordPublicUser` | `resetPassword` auth operation |
|
||||
| `unlockPublicUser` | `unlock` auth operation |
|
||||
| `verifyPublicUser` | `verify` auth operation |
|
||||
| `loginPublicUser` | `login` auth operation |
|
||||
| `logoutPublicUser` | `logout` auth operation |
|
||||
| `refreshTokenPublicUser` | `refresh` auth operation |
|
||||
|
||||
## Globals
|
||||
|
||||
Globals are also fully supported. For example:
|
||||
|
||||
```ts
|
||||
import { GlobalConfig } from 'payload/types';
|
||||
import type { GlobalConfig } from 'payload';
|
||||
|
||||
const Header: GlobalConfig = {
|
||||
slug: 'header',
|
||||
@@ -83,13 +83,13 @@ const Header: GlobalConfig = {
|
||||
|
||||
| Query Name | Operation |
|
||||
| ------------ | --------- |
|
||||
| **`Header`** | `findOne` |
|
||||
| `Header` | `findOne` |
|
||||
|
||||
**And the following mutation:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ------------------ | --------- |
|
||||
| **`updateHeader`** | `update` |
|
||||
| `updateHeader` | `update` |
|
||||
|
||||
## Preferences
|
||||
|
||||
@@ -99,14 +99,14 @@ User [preferences](/docs/admin/overview#preferences) for the admin panel are als
|
||||
|
||||
| Query Name | Operation |
|
||||
| ---------------- | --------- |
|
||||
| **`Preference`** | `findOne` |
|
||||
| `Preference` | `findOne` |
|
||||
|
||||
**And the following mutations:**
|
||||
|
||||
| Query Name | Operation |
|
||||
| ---------------------- | --------- |
|
||||
| **`updatePreference`** | `update` |
|
||||
| **`deletePreference`** | `delete` |
|
||||
| `updatePreference` | `update` |
|
||||
| `deletePreference` | `delete` |
|
||||
|
||||
## GraphQL Playground
|
||||
|
||||
@@ -119,7 +119,7 @@ You can even log in using the `login[collection-singular-label-here]` mutation t
|
||||
<br />
|
||||
To see more regarding how the above queries and mutations are used, visit your GraphQL playground
|
||||
(by default at
|
||||
[http://localhost:3000/api/graphql-playground](http://localhost:3000/api/graphql-playground))
|
||||
[`${SERVER_URL}/api/graphql-playground`](http://localhost:3000/api/graphql-playground))
|
||||
while your server is running. There, you can use the "Schema" and "Docs" buttons on the right to
|
||||
see a ton of detail about how GraphQL operates within Payload.
|
||||
</Banner>
|
||||
|
||||
@@ -34,7 +34,7 @@ All collection Hook properties accept arrays of synchronous or asynchronous func
|
||||
`collections/exampleHooks.js`
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types';
|
||||
import type { CollectionConfig } from 'payload';
|
||||
|
||||
export const ExampleHooks: CollectionConfig = {
|
||||
slug: 'example-hooks',
|
||||
@@ -70,7 +70,7 @@ The `beforeOperation` hook can be used to modify the arguments that operations a
|
||||
Available Collection operations include `create`, `read`, `update`, `delete`, `login`, `refresh`, and `forgotPassword`.
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeOperationHook } from 'payload/types'
|
||||
import type { CollectionBeforeOperationHook } from 'payload'
|
||||
|
||||
const beforeOperationHook: CollectionBeforeOperationHook = async ({
|
||||
args, // original arguments passed into the operation
|
||||
@@ -92,7 +92,7 @@ Please do note that this does not run before the client-side validation. If you
|
||||
3. `validate` runs on the server
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeValidateHook } from 'payload/types'
|
||||
import type { CollectionBeforeValidateHook } from 'payload'
|
||||
|
||||
const beforeValidateHook: CollectionBeforeValidateHook = async ({
|
||||
data, // incoming data to update or create with
|
||||
@@ -109,7 +109,7 @@ const beforeValidateHook: CollectionBeforeValidateHook = async ({
|
||||
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved.
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeChangeHook } from 'payload/types'
|
||||
import type { CollectionBeforeChangeHook } from 'payload'
|
||||
|
||||
const beforeChangeHook: CollectionBeforeChangeHook = async ({
|
||||
data, // incoming data to update or create with
|
||||
@@ -126,7 +126,7 @@ const beforeChangeHook: CollectionBeforeChangeHook = async ({
|
||||
After a document is created or updated, the `afterChange` hook runs. This hook is helpful to recalculate statistics such as total sales within a global, syncing user profile changes to a CRM, and more.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterChangeHook } from 'payload/types'
|
||||
import type { CollectionAfterChangeHook } from 'payload'
|
||||
|
||||
const afterChangeHook: CollectionAfterChangeHook = async ({
|
||||
doc, // full document data
|
||||
@@ -143,7 +143,7 @@ const afterChangeHook: CollectionAfterChangeHook = async ({
|
||||
Runs before `find` and `findByID` operations are transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument.
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeReadHook } from 'payload/types'
|
||||
import type { CollectionBeforeReadHook } from 'payload'
|
||||
|
||||
const beforeReadHook: CollectionBeforeReadHook = async ({
|
||||
doc, // full document data
|
||||
@@ -159,7 +159,7 @@ const beforeReadHook: CollectionBeforeReadHook = async ({
|
||||
Runs as the last step before documents are returned. Flattens locales, hides protected fields, and removes fields that users do not have access to.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterReadHook } from 'payload/types'
|
||||
import type { CollectionAfterReadHook } from 'payload'
|
||||
|
||||
const afterReadHook: CollectionAfterReadHook = async ({
|
||||
doc, // full document data
|
||||
@@ -176,7 +176,7 @@ const afterReadHook: CollectionAfterReadHook = async ({
|
||||
Runs before the `delete` operation. Returned values are discarded.
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeDeleteHook } from 'payload/types';
|
||||
import type { CollectionBeforeDeleteHook } from 'payload';
|
||||
|
||||
const beforeDeleteHook: CollectionBeforeDeleteHook = async ({
|
||||
req, // full Request object
|
||||
@@ -189,7 +189,7 @@ const beforeDeleteHook: CollectionBeforeDeleteHook = async ({
|
||||
Runs immediately after the `delete` operation removes records from the database. Returned values are discarded.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterDeleteHook } from 'payload/types';
|
||||
import type { CollectionAfterDeleteHook } from 'payload';
|
||||
|
||||
const afterDeleteHook: CollectionAfterDeleteHook = async ({
|
||||
req, // full Request object
|
||||
@@ -205,7 +205,7 @@ The `afterOperation` hook can be used to modify the result of operations or exec
|
||||
Available Collection operations include `create`, `find`, `findByID`, `update`, `updateByID`, `delete`, `deleteByID`, `login`, `refresh`, and `forgotPassword`.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterOperationHook } from 'payload/types'
|
||||
import type { CollectionAfterOperationHook } from 'payload'
|
||||
|
||||
const afterOperationHook: CollectionAfterOperationHook = async ({
|
||||
args, // arguments passed into the operation
|
||||
@@ -222,7 +222,7 @@ const afterOperationHook: CollectionAfterOperationHook = async ({
|
||||
For auth-enabled Collections, this hook runs during `login` operations where a user with the provided credentials exist, but before a token is generated and added to the response. You can optionally modify the user that is returned, or throw an error in order to deny the login operation.
|
||||
|
||||
```ts
|
||||
import { CollectionBeforeLoginHook } from 'payload/types'
|
||||
import type { CollectionBeforeLoginHook } from 'payload'
|
||||
|
||||
const beforeLoginHook: CollectionBeforeLoginHook = async ({
|
||||
req, // full Request object
|
||||
@@ -237,7 +237,7 @@ const beforeLoginHook: CollectionBeforeLoginHook = async ({
|
||||
For auth-enabled Collections, this hook runs after successful `login` operations. You can optionally modify the user that is returned.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterLoginHook } from 'payload/types';
|
||||
import type { CollectionAfterLoginHook } from 'payload';
|
||||
|
||||
const afterLoginHook: CollectionAfterLoginHook = async ({
|
||||
req, // full Request object
|
||||
@@ -251,7 +251,7 @@ const afterLoginHook: CollectionAfterLoginHook = async ({
|
||||
For auth-enabled Collections, this hook runs after `logout` operations.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterLogoutHook } from 'payload/types';
|
||||
import type { CollectionAfterLogoutHook } from 'payload';
|
||||
|
||||
const afterLogoutHook: CollectionAfterLogoutHook = async ({
|
||||
req, // full Request object
|
||||
@@ -263,7 +263,7 @@ const afterLogoutHook: CollectionAfterLogoutHook = async ({
|
||||
For auth-enabled Collections, this hook runs after `refresh` operations.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterRefreshHook } from 'payload/types';
|
||||
import type { CollectionAfterRefreshHook } from 'payload';
|
||||
|
||||
const afterRefreshHook: CollectionAfterRefreshHook = async ({
|
||||
req, // full Request object
|
||||
@@ -277,7 +277,7 @@ const afterRefreshHook: CollectionAfterRefreshHook = async ({
|
||||
For auth-enabled Collections, this hook runs after `me` operations.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterMeHook } from 'payload/types';
|
||||
import type { CollectionAfterMeHook } from 'payload';
|
||||
|
||||
const afterMeHook: CollectionAfterMeHook = async ({
|
||||
req, // full Request object
|
||||
@@ -290,7 +290,7 @@ const afterMeHook: CollectionAfterMeHook = async ({
|
||||
For auth-enabled Collections, this hook runs after successful `forgotPassword` operations. Returned values are discarded.
|
||||
|
||||
```ts
|
||||
import { CollectionAfterForgotPasswordHook } from 'payload/types'
|
||||
import type { CollectionAfterForgotPasswordHook } from 'payload'
|
||||
|
||||
const afterForgotPasswordHook: CollectionAfterForgotPasswordHook = async ({
|
||||
args, // arguments passed into the operation
|
||||
@@ -319,5 +319,5 @@ import type {
|
||||
CollectionAfterRefreshHook,
|
||||
CollectionAfterMeHook,
|
||||
CollectionAfterForgotPasswordHook,
|
||||
} from 'payload/types'
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
@@ -30,7 +30,7 @@ functionalities to be easily reusable across your projects.
|
||||
Example field configuration:
|
||||
|
||||
```ts
|
||||
import { Field } from 'payload/types';
|
||||
import type { Field } from 'payload';
|
||||
|
||||
const ExampleField: Field = {
|
||||
name: 'name',
|
||||
@@ -107,7 +107,7 @@ Runs before the `update` operation. This hook allows you to pre-process or forma
|
||||
validation.
|
||||
|
||||
```ts
|
||||
import { Field } from 'payload/types'
|
||||
import type { Field } from 'payload'
|
||||
|
||||
const usernameField: Field = {
|
||||
name: 'username',
|
||||
@@ -134,7 +134,7 @@ you can be confident that the field data that will be saved to the document is v
|
||||
validations.
|
||||
|
||||
```ts
|
||||
import { Field } from 'payload/types'
|
||||
import type { Field } from 'payload'
|
||||
|
||||
const emailField: Field = {
|
||||
name: 'email',
|
||||
@@ -162,7 +162,7 @@ The `afterChange` hook is executed after a field's value has been changed and sa
|
||||
for post-processing or triggering side effects based on the new value of the field.
|
||||
|
||||
```ts
|
||||
import { Field } from 'payload/types'
|
||||
import type { Field } from 'payload'
|
||||
|
||||
const membershipStatusField: Field = {
|
||||
name: 'membershipStatus',
|
||||
@@ -199,7 +199,7 @@ The `afterRead` hook is invoked after a field value is read from the database. T
|
||||
transforming the field data for output.
|
||||
|
||||
```ts
|
||||
import { Field } from 'payload/types'
|
||||
import type { Field } from 'payload'
|
||||
|
||||
const dateField: Field = {
|
||||
name: 'createdAt',
|
||||
@@ -230,7 +230,7 @@ By Default, unique and required text fields Payload will append "- Copy" to the
|
||||
Here is an example of a number field with a hook that increments the number to avoid unique constraint errors when duplicating a document:
|
||||
|
||||
```ts
|
||||
import { Field } from 'payload/types'
|
||||
import type { Field } from 'payload'
|
||||
|
||||
const numberField: Field = {
|
||||
name: 'number',
|
||||
@@ -249,7 +249,7 @@ const numberField: Field = {
|
||||
Payload exports a type for field hooks which can be accessed and used as follows:
|
||||
|
||||
```ts
|
||||
import type { FieldHook } from 'payload/types'
|
||||
import type { FieldHook } from 'payload'
|
||||
|
||||
// Field hook type is a generic that takes three arguments:
|
||||
// 1: The document type
|
||||
|
||||
@@ -21,7 +21,7 @@ All Global Hook properties accept arrays of synchronous or asynchronous function
|
||||
`globals/example-hooks.js`
|
||||
|
||||
```ts
|
||||
import { GlobalConfig } from 'payload/types';
|
||||
import type { GlobalConfig } from 'payload';
|
||||
|
||||
const ExampleHooks: GlobalConfig = {
|
||||
slug: 'header',
|
||||
@@ -43,7 +43,7 @@ const ExampleHooks: GlobalConfig = {
|
||||
Runs before the `update` operation. This hook allows you to add or format data before the incoming data is validated.
|
||||
|
||||
```ts
|
||||
import { GlobalBeforeValidateHook } from 'payload/types'
|
||||
import type { GlobalBeforeValidateHook } from 'payload'
|
||||
|
||||
const beforeValidateHook: GlobalBeforeValidateHook = async ({
|
||||
data, // incoming data to update or create with
|
||||
@@ -59,7 +59,7 @@ const beforeValidateHook: GlobalBeforeValidateHook = async ({
|
||||
Immediately following validation, `beforeChange` hooks will run within the `update` operation. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved.
|
||||
|
||||
```ts
|
||||
import { GlobalBeforeChangeHook } from 'payload/types'
|
||||
import type { GlobalBeforeChangeHook } from 'payload'
|
||||
|
||||
const beforeChangeHook: GlobalBeforeChangeHook = async ({
|
||||
data, // incoming data to update or create with
|
||||
@@ -75,7 +75,7 @@ const beforeChangeHook: GlobalBeforeChangeHook = async ({
|
||||
After a global is updated, the `afterChange` hook runs. Use this hook to purge caches of your applications, sync site data to CRMs, and more.
|
||||
|
||||
```ts
|
||||
import { GlobalAfterChangeHook } from 'payload/types'
|
||||
import type { GlobalAfterChangeHook } from 'payload'
|
||||
|
||||
const afterChangeHook: GlobalAfterChangeHook = async ({
|
||||
doc, // full document data
|
||||
@@ -91,7 +91,7 @@ const afterChangeHook: GlobalAfterChangeHook = async ({
|
||||
Runs before `findOne` global operation is transformed for output by `afterRead`. This hook fires before hidden fields are removed and before localized fields are flattened into the requested locale. Using this Hook will provide you with all locales and all hidden fields via the `doc` argument.
|
||||
|
||||
```ts
|
||||
import { GlobalBeforeReadHook } from 'payload/types'
|
||||
import type { GlobalBeforeReadHook } from 'payload'
|
||||
|
||||
const beforeReadHook: GlobalBeforeReadHook = async ({
|
||||
doc, // full document data
|
||||
@@ -104,7 +104,7 @@ const beforeReadHook: GlobalBeforeReadHook = async ({
|
||||
Runs as the last step before a global is returned. Flattens locales, hides protected fields, and removes fields that users do not have access to.
|
||||
|
||||
```ts
|
||||
import { GlobalAfterReadHook } from 'payload/types'
|
||||
import type { GlobalAfterReadHook } from 'payload'
|
||||
|
||||
const afterReadHook: GlobalAfterReadHook = async ({
|
||||
doc, // full document data
|
||||
@@ -124,5 +124,5 @@ import type {
|
||||
GlobalAfterChangeHook,
|
||||
GlobalBeforeReadHook,
|
||||
GlobalAfterReadHook,
|
||||
} from 'payload/types'
|
||||
} from 'payload'
|
||||
```
|
||||
|
||||
@@ -91,13 +91,13 @@ import { vercelStegaSplit } from '@vercel/stega'
|
||||
const { cleaned, encoded } = vercelStegaSplit(text)
|
||||
```
|
||||
|
||||
### Block fields
|
||||
### Blocks and array fields
|
||||
|
||||
All `blocks` fields by definition do not have plain text strings to encode. For this reason, blocks are given an additional `encodedSourceMap` key, which you can use to enable Content Link on entire sections of your site. You can then specify the editing container by adding the `data-vercel-edit-target` HTML attribute to any top-level element of your block.
|
||||
All `blocks` and `array` fields by definition do not have plain text strings to encode. For this reason, they are given an additional `_encodedSourceMap` property, which you can use to enable Content Link on entire _sections_ of your site. You can then specify the editing container by adding the `data-vercel-edit-target` HTML attribute to any top-level element of your block.
|
||||
|
||||
```ts
|
||||
<div data-vercel-edit-target>
|
||||
<span style={{ display: "none" }}>{encodedSourceMap}</span>
|
||||
<span style={{ display: "none" }}>{_encodedSourceMap}</span>
|
||||
{children}
|
||||
</div>
|
||||
```
|
||||
|
||||
824
docs/lexical/building-custom-features.mdx
Normal file
824
docs/lexical/building-custom-features.mdx
Normal file
@@ -0,0 +1,824 @@
|
||||
---
|
||||
title: Lexical Building Custom Features
|
||||
label: Custom Features
|
||||
order: 40
|
||||
desc: Building custom lexical features
|
||||
keywords: lexical, rich text, editor, headless cms, feature, features
|
||||
---
|
||||
|
||||
Before you begin building custom features for Lexical, it is crucial to familiarize yourself with the [Lexical docs](https://lexical.dev/docs/intro), particularly the "Concepts" section. This foundation is necessary for understanding Lexical's core principles, such as nodes, editor state, and commands.
|
||||
|
||||
Lexical features are designed to be modular, meaning each piece of functionality is encapsulated within just two specific interfaces: one for server-side code and one for client-side code.
|
||||
|
||||
By convention, these are named feature.server.ts for server-side functionality and feature.client.ts for client-side functionality. The primary functionality is housed within feature.server.ts, which users will import into their projects. The client-side feature, although defined separately, is integrated and rendered server-side through the server feature. That way, we still maintain a clear boundary between server and client code, while also centralizing the code needed for a feature in basically one place. This approach is beneficial for managing all the bits and pieces which make up your feature as a whole, such as toolbar entries, buttons, or new nodes, allowing each feature to be neatly contained and managed independently.
|
||||
|
||||
|
||||
## Server Feature
|
||||
|
||||
To start building new features, you should start with the server feature, which is the entry-point.
|
||||
|
||||
**Example myFeature/feature.server.ts:**
|
||||
|
||||
```ts
|
||||
import { createServerFeature } from '@payloadcms/richtext-lexical';
|
||||
|
||||
export const MyFeature = createServerFeature({
|
||||
feature: {
|
||||
},
|
||||
key: 'myFeature',
|
||||
})
|
||||
```
|
||||
|
||||
`createServerFeature` is a helper function which lets you create new features without boilerplate code.
|
||||
|
||||
Now, the feature is ready to be used in the editor:
|
||||
|
||||
```ts
|
||||
import { MyFeature } from './myFeature/feature.server';
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical';
|
||||
|
||||
//...
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: [
|
||||
MyFeature(),
|
||||
],
|
||||
}),
|
||||
},
|
||||
```
|
||||
|
||||
By default, this server feature does nothing - you haven't added any functionality yet. Depending on what you want your
|
||||
feature to do, the ServerFeature type exposes various properties you can set to inject custom functionality into the lexical editor.
|
||||
|
||||
### i18n
|
||||
|
||||
Each feature can register their own translations, which are automatically scoped to the feature key:
|
||||
|
||||
```ts
|
||||
import { createServerFeature } from '@payloadcms/richtext-lexical';
|
||||
|
||||
|
||||
export const MyFeature = createServerFeature({
|
||||
feature: {
|
||||
i18n: {
|
||||
en: {
|
||||
label: 'My Feature',
|
||||
},
|
||||
de: {
|
||||
label: 'Mein Feature',
|
||||
},
|
||||
},
|
||||
},
|
||||
key: 'myFeature',
|
||||
})
|
||||
```
|
||||
|
||||
This allows you to add i18n translations scoped to your feature. This specific example translation will be available under `lexical:myFeature:label` - `myFeature` being your feature key.
|
||||
|
||||
### Markdown Transformers
|
||||
|
||||
The Server Feature, just like the Client Feature, allows you to add markdown transformers. Markdown transformers on the server are used when [converting the editor from or to markdown](/docs/lexical/converters#markdown-lexical).
|
||||
|
||||
```ts
|
||||
import { createServerFeature } from '@payloadcms/richtext-lexical';
|
||||
import type { ElementTransformer } from '@lexical/markdown'
|
||||
import {
|
||||
$createMyNode,
|
||||
$isMyNode,
|
||||
MyNode
|
||||
} from './nodes/MyNode'
|
||||
|
||||
const MyMarkdownTransformer: ElementTransformer = {
|
||||
type: 'element',
|
||||
dependencies: [MyNode],
|
||||
export: (node, exportChildren) => {
|
||||
if (!$isMyNode(node)) {
|
||||
return null
|
||||
}
|
||||
return '+++'
|
||||
},
|
||||
// match ---
|
||||
regExp: /^+++\s*$/,
|
||||
replace: (parentNode) => {
|
||||
const node = $createMyNode()
|
||||
if (node) {
|
||||
parentNode.replace(node)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export const MyFeature = createServerFeature({
|
||||
feature: {
|
||||
markdownTransformers: [MyMarkdownTransformer],
|
||||
},
|
||||
key: 'myFeature',
|
||||
})
|
||||
```
|
||||
|
||||
In this example, the node will be outputted as `+++` in Markdown, and the markdown `+++` will be converted to a `MyNode` node in the editor.
|
||||
|
||||
### Nodes
|
||||
|
||||
While nodes added to the server feature do not control how the node is rendered in the editor, they control other aspects of the node:
|
||||
- HTML conversion
|
||||
- Node Hooks
|
||||
- Sub fields
|
||||
- Behavior in a headless editor
|
||||
|
||||
The `createNode` helper function is used to create nodes with proper typing. It is recommended to use this function to create nodes.
|
||||
|
||||
```ts
|
||||
import { createServerFeature, createNode } from '@payloadcms/richtext-lexical';
|
||||
import {
|
||||
MyNode
|
||||
} from './nodes/MyNode'
|
||||
|
||||
export const MyFeature = createServerFeature({
|
||||
feature: {
|
||||
|
||||
nodes: [
|
||||
// Use the createNode helper function to more easily create nodes with proper typing
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: () => {
|
||||
return `<hr/>`
|
||||
},
|
||||
nodeTypes: [MyNode.getType()],
|
||||
},
|
||||
},
|
||||
// Here you can add your actual node. On the server, they will be
|
||||
// used to initialize a headless editor which can be used to perform
|
||||
// operations on the editor, like markdown / html conversion.
|
||||
node: MyNode,
|
||||
}),
|
||||
],
|
||||
},
|
||||
key: 'myFeature',
|
||||
})
|
||||
```
|
||||
|
||||
While nodes in the client feature are added by themselves to the nodes array, nodes in the server feature can be added together with the following sibling options:
|
||||
|
||||
| Option | Description |
|
||||
|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`getSubFields`** | If a node includes sub-fields (e.g. block and link nodes), passing the subFields schema here will make payload automatically populate & run hooks for them. |
|
||||
| **`getSubFieldsData`** | If a node includes sub-fields, the sub-fields data needs to be returned here, alongside `getSubFields` which returns their schema. |
|
||||
| **`graphQLPopulationPromises`** | Allows you to run population logic when a node's data was requested from GraphQL. While `getSubFields` and `getSubFieldsData` automatically handle populating sub-fields (since they run hooks on them), those are only populated in the Rest API. This is because the Rest API hooks do not have access to the 'depth' property provided by GraphQL. In order for them to be populated correctly in GraphQL, the population logic needs to be provided here. |
|
||||
| **`node`** | The actual lexical node needs to be provided here. This also supports [lexical node replacements](https://lexical.dev/docs/concepts/node-replacement). |
|
||||
| **`validations`** | This allows you to provide node validations, which are run when your document is being validated, alongside other payload fields. You can use it to throw a validation error for a specific node in case its data is incorrect. |
|
||||
| **`converters`** | Allows you to define how a node can be serialized into different formats. Currently, only supports HTML. Markdown converters are defined in `markdownTransformers` and not here. |
|
||||
| **`hooks`** | Just like payload fields, you can provide hooks which are run for this specific node. These are called Node Hooks. |
|
||||
|
||||
### Feature load order
|
||||
|
||||
Server features can also accept a function as the `feature` property (useful for sanitizing props, as mentioned below). This function will be called when the feature is loaded during the payload sanitization process:
|
||||
|
||||
```ts
|
||||
import { createServerFeature } from '@payloadcms/richtext-lexical';
|
||||
|
||||
createServerFeature({
|
||||
//...
|
||||
feature: async ({ config, isRoot, props, resolvedFeatures, unSanitizedEditorConfig, featureProviderMap }) => {
|
||||
|
||||
return {
|
||||
//Actual server feature here...
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
"Loading" here means the process of calling this `feature` function. By default, features are called in the order in which they are added to the editor.
|
||||
However, sometimes you might want to load a feature after another feature has been loaded, or require a different feature to be loaded, throwing an error if this is not the case.
|
||||
|
||||
Within lexical, one example where this is done are our list features. Both `UnorderedListFeature` and `OrderedListFeature` register the same `ListItem` node. Within `UnorderedListFeature` we register it normally, but within `OrderedListFeature` we want to only register the `ListItem` node if the `UnorderedListFeature` is not present - otherwise, we would have two features registering the same node.
|
||||
|
||||
Here is how we do it:
|
||||
|
||||
```ts
|
||||
import { createServerFeature, createNode } from '@payloadcms/richtext-lexical';
|
||||
|
||||
export const OrderedListFeature = createServerFeature({
|
||||
feature: ({ featureProviderMap }) => {
|
||||
return {
|
||||
// ...
|
||||
nodes: featureProviderMap.has('unorderedList')
|
||||
? []
|
||||
: [
|
||||
createNode({
|
||||
// ...
|
||||
}),
|
||||
],
|
||||
}
|
||||
},
|
||||
key: 'orderedList',
|
||||
})
|
||||
```
|
||||
|
||||
`featureProviderMap` will always be available and contain all the features, even yet-to-be-loaded ones, so we can check if a feature is loaded by checking if its `key` present in the map.
|
||||
|
||||
If you wanted to make sure a feature is loaded before another feature, you can use the `dependenciesPriority` property:
|
||||
|
||||
```ts
|
||||
import { createServerFeature } from '@payloadcms/richtext-lexical';
|
||||
|
||||
export const MyFeature = createServerFeature({
|
||||
feature: ({ featureProviderMap }) => {
|
||||
return {
|
||||
// ...
|
||||
}
|
||||
},
|
||||
key: 'myFeature',
|
||||
dependenciesPriority: ['otherFeature'],
|
||||
})
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`dependenciesSoft`** | Keys of soft-dependencies needed for this feature. These are optional. Payload will attempt to load them before this feature, but doesn't throw an error if that's not possible. |
|
||||
| **`dependencies`** | Keys of dependencies needed for this feature. These dependencies do not have to be loaded first, but they have to exist, otherwise an error will be thrown. |
|
||||
| **`dependenciesPriority`** | Keys of priority dependencies needed for this feature. These dependencies have to be loaded first AND have to exist, otherwise an error will be thrown. They will be available in the `feature` property. |
|
||||
|
||||
## Client Feature
|
||||
|
||||
Most of the functionality which the user actually sees and interacts with, like toolbar items and React components for nodes, resides on the client-side.
|
||||
|
||||
To set up your client-side feature, follow these three steps:
|
||||
|
||||
1. **Create a Separate File**: Start by creating a new file specifically for your client feature, such as `myFeature/feature.client.ts`. It's important to keep client and server features in separate files to maintain a clean boundary between server and client code.
|
||||
2. **'use client'**: Mark that file with a 'use client' directive at the top of the file
|
||||
3. **Register the Client Feature**: Register the client feature within your server feature, by passing it to the `ClientFeature` prop. This is needed because the server feature is the sole entry-point of your feature. This also means you are not able to create a client feature without a server feature, as you will not be able to register it otherwise.
|
||||
|
||||
**Example myFeature/feature.client.ts:**
|
||||
|
||||
```ts
|
||||
'use client'
|
||||
|
||||
import { createClientFeature } from '@payloadcms/richtext-lexical/client';
|
||||
|
||||
export const MyClientFeature = createClientFeature({
|
||||
|
||||
})
|
||||
```
|
||||
|
||||
Explore the APIs available through ClientFeature to add the specific functionality you need. Remember, do not import directly from `'@payloadcms/richtext-lexical'` when working on the client-side, as it will cause errors with webpack or turbopack. Instead, use `'@payloadcms/richtext-lexical/client'` for all client-side imports. Type-imports are excluded from this rule and can always be imported.
|
||||
|
||||
### Nodes
|
||||
|
||||
Add nodes to the `nodes` array in **both** your client & server feature. On the server side, nodes are utilized for backend operations like HTML conversion in a headless editor. On the client side, these nodes are integral to how content is displayed and managed in the editor, influencing how they are rendered, behave, and saved in the database.
|
||||
|
||||
Example:
|
||||
|
||||
**myFeature/feature.client.ts:**
|
||||
|
||||
```ts
|
||||
'use client'
|
||||
|
||||
import { createClientFeature } from '@payloadcms/richtext-lexical/client';
|
||||
import { MyNode } from './nodes/MyNode';
|
||||
|
||||
export const MyClientFeature = createClientFeature({
|
||||
nodes: [MyNode]
|
||||
})
|
||||
```
|
||||
|
||||
This also supports [lexical node replacements](https://lexical.dev/docs/concepts/node-replacement).
|
||||
|
||||
**myFeature/nodes/MyNode.tsx:**
|
||||
|
||||
Here is a basic DecoratorNode example:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
DOMConversionMap,
|
||||
DOMConversionOutput,
|
||||
DOMExportOutput,
|
||||
EditorConfig,
|
||||
LexicalNode,
|
||||
SerializedLexicalNode,
|
||||
} from 'lexical'
|
||||
|
||||
import { $applyNodeReplacement, DecoratorNode } from 'lexical'
|
||||
|
||||
// SerializedLexicalNode is the default lexical node.
|
||||
// By setting your SerializedMyNode type to SerializedLexicalNode,
|
||||
// you are basically saying that this node does not save any additional data.
|
||||
// If you want your node to save data, feel free to extend it
|
||||
export type SerializedMyNode = SerializedLexicalNode
|
||||
|
||||
// Lazy-import the React component to your node here
|
||||
const MyNodeComponent = React.lazy(() =>
|
||||
import('../component/index.js').then((module) => ({
|
||||
default: module.MyNodeComponent,
|
||||
})),
|
||||
)
|
||||
|
||||
/**
|
||||
* This node is a DecoratorNode. DecoratorNodes allow you to render React components in the editor.
|
||||
*
|
||||
* They need both createDom and decorate functions. createDom => outside of the html. decorate => React Component inside of the html.
|
||||
*
|
||||
* If we used DecoratorBlockNode instead, we would only need a decorate method
|
||||
*/
|
||||
export class MyNode extends DecoratorNode<React.ReactElement> {
|
||||
static clone(node: MyNode): MyNode {
|
||||
return new MyNode(node.__key)
|
||||
}
|
||||
|
||||
static getType(): string {
|
||||
return 'myNode'
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines what happens if you copy a div element from another page and paste it into the lexical editor
|
||||
*
|
||||
* This also determines the behavior of lexical's internal HTML -> Lexical converter
|
||||
*/
|
||||
static importDOM(): DOMConversionMap | null {
|
||||
return {
|
||||
div: () => ({
|
||||
conversion: $yourConversionMethod,
|
||||
priority: 0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The data for this node is stored serialized as JSON. This is the "load function" of that node: it takes the saved data and converts it into a node.
|
||||
*/
|
||||
static importJSON(serializedNode: SerializedMyNode): MyNode {
|
||||
return $createMyNode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how the hr element is rendered in the lexical editor. This is only the "initial" / "outer" HTML element.
|
||||
*/
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const element = document.createElement('div')
|
||||
return element
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to render a React component within whatever createDOM returns.
|
||||
*/
|
||||
decorate(): React.ReactElement {
|
||||
return <MyNodeComponent nodeKey={this.__key} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Opposite of importDOM, this function defines what happens when you copy a div element from the lexical editor and paste it into another page.
|
||||
*
|
||||
* This also determines the behavior of lexical's internal Lexical -> HTML converter
|
||||
*/
|
||||
exportDOM(): DOMExportOutput {
|
||||
return { element: document.createElement('div') }
|
||||
}
|
||||
/**
|
||||
* Opposite of importJSON. This determines what data is saved in the database / in the lexical editor state.
|
||||
*/
|
||||
exportJSON(): SerializedLexicalNode {
|
||||
return {
|
||||
type: 'myNode',
|
||||
version: 1,
|
||||
}
|
||||
}
|
||||
|
||||
getTextContent(): string {
|
||||
return '\n'
|
||||
}
|
||||
|
||||
isInline(): false {
|
||||
return false
|
||||
}
|
||||
|
||||
updateDOM(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// This is used in the importDOM method. Totally optional if you do not want your node to be created automatically when copy & pasting certain dom elements
|
||||
// into your editor.
|
||||
function $yourConversionMethod(): DOMConversionOutput {
|
||||
return { node: $createMyNode() }
|
||||
}
|
||||
|
||||
// This is a utility method to create a new MyNode. Utility methods prefixed with $ make it explicit that this should only be used within lexical
|
||||
export function $createMyNode(): MyNode {
|
||||
return $applyNodeReplacement(new MyNode())
|
||||
}
|
||||
|
||||
// This is just a utility method you can use to check if a node is a MyNode. This also ensures correct typing.
|
||||
export function $isMyNode(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is MyNode {
|
||||
return node instanceof MyNode
|
||||
}
|
||||
```
|
||||
|
||||
Please do not add any 'use client' directives to your nodes, as the node class can be used on the server.
|
||||
|
||||
### Plugins
|
||||
|
||||
One small part of a feature are plugins. The name stems from the lexical playground plugins and is just a small part of a lexical feature.
|
||||
Plugins are simply React components which are added to the editor, within all the lexical context providers. They can be used to add any functionality
|
||||
to the editor, by utilizing the lexical API.
|
||||
|
||||
Most commonly, they are used to register [lexical listeners](https://lexical.dev/docs/concepts/listeners), [node transforms](https://lexical.dev/docs/concepts/transforms) or [commands](https://lexical.dev/docs/concepts/commands).
|
||||
For example, you could add a drawer to your plugin and register a command which opens it. That command can then be called from anywhere within lexical, e.g. from within your custom lexical node.
|
||||
|
||||
To add a plugin, simply add it to the `plugins` array in your client feature:
|
||||
|
||||
```ts
|
||||
'use client'
|
||||
|
||||
import { createClientFeature } from '@payloadcms/richtext-lexical/client';
|
||||
import { MyPlugin } from './plugin';
|
||||
|
||||
export const MyClientFeature = createClientFeature({
|
||||
plugins: [MyPlugin]
|
||||
})
|
||||
```
|
||||
|
||||
Example plugin.tsx:
|
||||
|
||||
```ts
|
||||
'use client'
|
||||
import type {
|
||||
LexicalCommand,
|
||||
} from 'lexical'
|
||||
|
||||
import {
|
||||
createCommand,
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
} from 'lexical'
|
||||
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext.js'
|
||||
import { $insertNodeToNearestRoot } from '@lexical/utils'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import type { PluginComponent } from '@payloadcms/richtext-lexical' // type imports can be imported from @payloadcms/richtext-lexical - even on the client
|
||||
|
||||
import {
|
||||
$createMyNode,
|
||||
} from '../nodes/MyNode'
|
||||
import './index.scss'
|
||||
|
||||
export const INSERT_MYNODE_COMMAND: LexicalCommand<void> = createCommand(
|
||||
'INSERT_MYNODE_COMMAND',
|
||||
)
|
||||
|
||||
/**
|
||||
* Plugin which registers a lexical command to insert a new MyNode into the editor
|
||||
*/
|
||||
export const MyNodePlugin: PluginComponent= () => {
|
||||
// The useLexicalComposerContext hook can be used to access the lexical editor instance
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
return editor.registerCommand(
|
||||
INSERT_MYNODE_COMMAND,
|
||||
(type) => {
|
||||
const selection = $getSelection()
|
||||
|
||||
if (!$isRangeSelection(selection)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const focusNode = selection.focus.getNode()
|
||||
|
||||
if (focusNode !== null) {
|
||||
const newMyNode = $createMyNode()
|
||||
$insertNodeToNearestRoot(newMyNode)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
)
|
||||
}, [editor])
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we register a lexical command, which simply inserts a new MyNode into the editor. This command can be called from anywhere within lexical, e.g. from within a custom node.
|
||||
|
||||
### Toolbar groups
|
||||
|
||||
Toolbar groups are visual containers which hold toolbar items. There are different toolbar group types which determine *how* a toolbar item is displayed: `dropdown` and `buttons`.
|
||||
|
||||
All the default toolbar groups are exported from `@payloadcms/richtext-lexical/client`. You can use them to add your own toolbar items to the editor:
|
||||
- Dropdown: `toolbarAddDropdownGroupWithItems`
|
||||
- Dropdown: `toolbarTextDropdownGroupWithItems`
|
||||
- Buttons: `toolbarFormatGroupWithItems`
|
||||
- Buttons: `toolbarFeatureButtonsGroupWithItems`
|
||||
|
||||
Within dropdown groups, items are positioned vertically when the dropdown is opened and include the icon & label. Within button groups, items are positioned horizontally and only include the icon. If a toolbar group with the same key is declared twice, all its items will be merged into one group.
|
||||
|
||||
#### Custom buttons toolbar group
|
||||
|
||||
| Option | Description |
|
||||
|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`items`** | All toolbar items part of this toolbar group need to be added here. |
|
||||
| **`key`** | Each toolbar group needs to have a unique key. Groups with the same keys will have their items merged together. |
|
||||
| **`order`** | Determines where the toolbar group will be. |
|
||||
| **`type`** | Controls the toolbar group type. Set to `buttons` to create a buttons toolbar group, which displays toolbar items horizontally using only their icons. |
|
||||
|
||||
Example:
|
||||
```ts
|
||||
import type { ToolbarGroup, ToolbarGroupItem } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const toolbarFormatGroupWithItems = (items: ToolbarGroupItem[]): ToolbarGroup => {
|
||||
return {
|
||||
type: 'buttons',
|
||||
items,
|
||||
key: 'myButtonsToolbar',
|
||||
order: 10,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom dropdown toolbar group
|
||||
|
||||
| Option | Description |
|
||||
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`items`** | All toolbar items part of this toolbar group need to be added here. |
|
||||
| **`key`** | Each toolbar group needs to have a unique key. Groups with the same keys will have their items merged together. |
|
||||
| **`order`** | Determines where the toolbar group will be. |
|
||||
| **`type`** | Controls the toolbar group type. Set to `dropdown` to create a buttons toolbar group, which displays toolbar items vertically using their icons and labels, if the dropdown is open. |
|
||||
| **`ChildComponent`** | The dropdown toolbar ChildComponent allows you to pass in a React Component which will be displayed within the dropdown button. |
|
||||
|
||||
Example:
|
||||
```ts
|
||||
import type { ToolbarGroup, ToolbarGroupItem } from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { MyIcon } from './icons/MyIcon'
|
||||
|
||||
export const toolbarAddDropdownGroupWithItems = (items: ToolbarGroupItem[]): ToolbarGroup => {
|
||||
return {
|
||||
type: 'dropdown',
|
||||
ChildComponent: MyIcon,
|
||||
items,
|
||||
key: 'myDropdownToolbar',
|
||||
order: 10,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Toolbar items
|
||||
|
||||
Custom nodes and features on its own are pointless, if they can't be added to the editor. You will need to hook in one of our interfaces which allow the user to interact with the editor:
|
||||
|
||||
- Fixed toolbar which stays fixed at the top of the editor
|
||||
- Inline, floating toolbar which appears when selecting text
|
||||
- Slash menu which appears when typing `/` in the editor
|
||||
- Markdown transformers, which are triggered when a certain text pattern is typed in the editor
|
||||
- Or any other interfaces which can be added via your own plugins. Our toolbars are a prime example of this - they are just plugins.
|
||||
|
||||
To add a toolbar item to either the floating or the inline toolbar, you can add a ToolbarGroup with a ToolbarItem to the `toolbarFixed` or `toolbarInline` props of your client feature:
|
||||
|
||||
```ts
|
||||
'use client'
|
||||
|
||||
import { createClientFeature, toolbarAddDropdownGroupWithItems } from '@payloadcms/richtext-lexical/client';
|
||||
import { IconComponent } from './icon';
|
||||
import { $isHorizontalRuleNode } from './nodes/MyNode';
|
||||
import { INSERT_MYNODE_COMMAND } from './plugin';
|
||||
import { $isNodeSelection } from 'lexical'
|
||||
|
||||
export const MyClientFeature = createClientFeature({
|
||||
toolbarFixed: {
|
||||
groups: [
|
||||
toolbarAddDropdownGroupWithItems([
|
||||
{
|
||||
ChildComponent: IconComponent,
|
||||
isActive: ({ selection }) => {
|
||||
if (!$isNodeSelection(selection) || !selection.getNodes().length) {
|
||||
return false
|
||||
}
|
||||
|
||||
const firstNode = selection.getNodes()[0]
|
||||
return $isHorizontalRuleNode(firstNode)
|
||||
},
|
||||
key: 'myNode',
|
||||
label: ({ i18n }) => {
|
||||
return i18n.t('lexical:myFeature:label')
|
||||
},
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_MYNODE_COMMAND, undefined)
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You will have to provide a toolbar group first, and then the items for that toolbar group (more on that above).
|
||||
|
||||
A `ToolbarItem` various props you can use to customize its behavior:
|
||||
|
||||
| Option | Description |
|
||||
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`ChildComponent`** | A React component which is rendered within your toolbar item's default button component. Usually, you want this to be an icon. |
|
||||
| **`Component`** | A React component which is rendered in place of the toolbar item's default button component, thus completely replacing it. The `ChildComponent` and `onSelect` properties will be ignored. |
|
||||
| **`label`** | The label will be displayed in your toolbar item, if it's within a dropdown group. To make use of i18n, this can be a function. |
|
||||
| **`key`** | Each toolbar item needs to have a unique key. |
|
||||
| **`onSelect`** | A function which is called when the toolbar item is clicked. |
|
||||
| **`isEnabled`** | This is optional and controls if the toolbar item is clickable or not. If `false` is returned here, it will be grayed out and unclickable. |
|
||||
| **`isActive`** | This is optional and controls if the toolbar item is highlighted or not |
|
||||
|
||||
The API for adding an item to the floating inline toolbar (`toolbarInline`) is identical. If you wanted to add an item to both the fixed and inline toolbar, you can extract it into its own variable
|
||||
(typed as `ToolbarGroup[]`) and add it to both the `toolbarFixed` and `toolbarInline` props.
|
||||
|
||||
### Slash Menu groups
|
||||
|
||||
We're exporting `slashMenuBasicGroupWithItems` from `@payloadcms/richtext-lexical/client` which you can use to add items to the slash menu labelled "Basic". If you want to create your own slash menu group, here is an example:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
SlashMenuGroup,
|
||||
SlashMenuItem,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
export function mwnSlashMenuGroupWithItems(items: SlashMenuItem[]): SlashMenuGroup {
|
||||
return {
|
||||
items,
|
||||
key: 'myGroup',
|
||||
label: 'My Group' // <= This can be a function to make use of i18n
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By creating a helper function like this, you can easily re-use it and add items to it. All Slash Menu groups with the same keys will have their items merged together.
|
||||
|
||||
| Option | Description |
|
||||
|-------------|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`items`** | An array of `SlashMenuItem`'s which will be displayed in the slash menu. |
|
||||
| **`label`** | The label will be displayed before your Slash Menu group. In order to make use of i18n, this can be a function. |
|
||||
| **`key`** | Used for class names and, if label is not provided, for display. Slash menus with the same key will have their items merged together. |
|
||||
|
||||
|
||||
### Slash Menu items
|
||||
|
||||
The API for adding items to the slash menu is similar. There are slash menu groups, and each slash menu groups has items. Here is an example:
|
||||
|
||||
```ts
|
||||
'use client'
|
||||
|
||||
import { createClientFeature, slashMenuBasicGroupWithItems } from '@payloadcms/richtext-lexical/client';
|
||||
import { INSERT_MYNODE_COMMAND } from './plugin';
|
||||
import { IconComponent } from './icon';
|
||||
|
||||
export const MyClientFeature = createClientFeature({
|
||||
slashMenu: {
|
||||
groups: [
|
||||
slashMenuBasicGroupWithItems([
|
||||
{
|
||||
Icon: IconComponent,
|
||||
key: 'myNode',
|
||||
keywords: ['myNode', 'myFeature', 'someOtherKeyword'],
|
||||
label: ({ i18n }) => {
|
||||
return i18n.t('lexical:myFeature:label')
|
||||
},
|
||||
onSelect: ({ editor }) => {
|
||||
editor.dispatchCommand(INSERT_MYNODE_COMMAND, undefined)
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`Icon`** | The icon which is rendered in your slash menu item. |
|
||||
| **`label`** | The label will be displayed in your slash menu item. In order to make use of i18n, this can be a function. |
|
||||
| **`key`** | Each slash menu item needs to have a unique key. The key will be matched when typing, displayed if no `label` property is set, and used for classNames. |
|
||||
| **`onSelect`** | A function which is called when the slash menu item is selected. |
|
||||
| **`keywords`** | Keywords are used to match the item for different texts typed after the '/'. E.g. you might want to show a horizontal rule item if you type both /hr, /separator, /horizontal etc. In addition to the keywords, the label and key will be used to find the right slash menu item. |
|
||||
|
||||
|
||||
### Markdown Transformers
|
||||
|
||||
The Client Feature, just like the Server Feature, allows you to add markdown transformers. Markdown transformers on the client are used to create new nodes when a certain markdown pattern is typed in the editor.
|
||||
|
||||
```ts
|
||||
import { createClientFeature } from '@payloadcms/richtext-lexical/client';
|
||||
import type { ElementTransformer } from '@lexical/markdown'
|
||||
import {
|
||||
$createMyNode,
|
||||
$isMyNode,
|
||||
MyNode
|
||||
} from './nodes/MyNode'
|
||||
|
||||
const MyMarkdownTransformer: ElementTransformer = {
|
||||
type: 'element',
|
||||
dependencies: [MyNode],
|
||||
export: (node, exportChildren) => {
|
||||
if (!$isMyNode(node)) {
|
||||
return null
|
||||
}
|
||||
return '+++'
|
||||
},
|
||||
// match ---
|
||||
regExp: /^+++\s*$/,
|
||||
replace: (parentNode) => {
|
||||
const node = $createMyNode()
|
||||
if (node) {
|
||||
parentNode.replace(node)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export const MyFeature = createClientFeature({
|
||||
markdownTransformers: [MyMarkdownTransformer],
|
||||
})
|
||||
```
|
||||
|
||||
In this example, a new `MyNode` will be inserted into the editor when `+++ ` is typed.
|
||||
|
||||
## Props
|
||||
|
||||
To accept props in your feature, type them as a generic.
|
||||
|
||||
Server Feature:
|
||||
|
||||
```ts
|
||||
createServerFeature<UnSanitizedProps, SanitizedProps, UnSanitizedClientProps>({
|
||||
//...
|
||||
})
|
||||
```
|
||||
|
||||
Client Feature:
|
||||
|
||||
```ts
|
||||
createClientFeature<UnSanitizedClientProps, SanitizedClientProps>({
|
||||
//...
|
||||
})
|
||||
```
|
||||
|
||||
The unSanitized props are what the user will pass to the feature when they call its provider function and add it to their editor config. You then have an option to sanitize those props.
|
||||
To sanitize those in the server feature, you can pass a function to `feature` instead of an object:
|
||||
|
||||
```ts
|
||||
createServerFeature<UnSanitizedProps, SanitizedProps, UnSanitizedClientProps>({
|
||||
//...
|
||||
feature: async ({ config, isRoot, props, resolvedFeatures, unSanitizedEditorConfig, featureProviderMap }) => {
|
||||
const sanitizedProps = doSomethingWithProps(props)
|
||||
|
||||
return {
|
||||
sanitizedServerFeatureProps: sanitizedProps,
|
||||
//Actual server feature here...
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Keep in mind that any sanitized props then have to be returned in the `sanitizedServerFeatureProps` property.
|
||||
|
||||
In the client feature, it works similarly:
|
||||
|
||||
```ts
|
||||
createClientFeature<UnSanitizedClientProps, SanitizedClientProps>(
|
||||
({ clientFunctions, featureProviderMap, props, resolvedFeatures, unSanitizedEditorConfig }) => {
|
||||
const sanitizedProps = doSomethingWithProps(props)
|
||||
return {
|
||||
sanitizedClientFeatureProps: sanitizedProps,
|
||||
//Actual client feature here...
|
||||
}
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Bringing props from the server to the client
|
||||
|
||||
By default, the client feature will never receive any props from the server feature. In order to pass props from the server to the client, you can need to return those props in the server feature:
|
||||
|
||||
```ts
|
||||
type UnSanitizedClientProps = {
|
||||
test: string
|
||||
}
|
||||
|
||||
createServerFeature<UnSanitizedProps, SanitizedProps, UnSanitizedClientProps>({
|
||||
//...
|
||||
feature: {
|
||||
clientFeatureProps: {
|
||||
test: 'myValue'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The reason the client feature does not have the same props available as the server by default is because all client props need to be serializable. You can totally accept things like functions or Maps as props in your server feature, but you will not be able to send those to the client. In the end, those props are sent from the server to the client over the network, so they need to be serializable.
|
||||
|
||||
## More information
|
||||
|
||||
Have a look at the [features we've already built](https://github.com/payloadcms/payload/tree/beta/packages/richtext-lexical/src/features) - understanding how they work will help you understand how to create your own. There is no difference between the features included by default and the ones you create yourself - since those features are all isolated from the "core", you have access to the same APIs, whether the feature is part of payload or not!
|
||||
@@ -6,7 +6,6 @@ desc: Conversion between lexical, markdown and html
|
||||
keywords: lexical, rich text, editor, headless cms, convert, html, mdx, markdown, md, conversion, export
|
||||
---
|
||||
|
||||
|
||||
## Lexical => HTML
|
||||
|
||||
Lexical saves data in JSON, but can also generate its HTML representation via two main methods:
|
||||
@@ -21,7 +20,7 @@ The editor comes with built-in HTML serializers, simplifying the process of conv
|
||||
To add HTML generation directly within the collection, follow the example below:
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { HTMLConverterFeature, lexicalEditor, lexicalHTML } from '@payloadcms/richtext-lexical'
|
||||
|
||||
@@ -47,33 +46,55 @@ const Pages: CollectionConfig = {
|
||||
|
||||
The `lexicalHTML()` function creates a new field that automatically converts the referenced lexical richText field into HTML through an afterRead hook.
|
||||
|
||||
### Generating HTML anywhere on the server:
|
||||
### Generating HTML anywhere on the server
|
||||
|
||||
If you wish to convert JSON to HTML ad-hoc, use this code snippet:
|
||||
If you wish to convert JSON to HTML ad-hoc, use the `convertLexicalToHTML` function:
|
||||
|
||||
```ts
|
||||
import type { SerializedEditorState } from 'lexical'
|
||||
import {
|
||||
type SanitizedEditorConfig,
|
||||
convertLexicalToHTML,
|
||||
consolidateHTMLConverters,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
import { consolidateHTMLConverters, convertLexicalToHTML } from '@payloadcms/richtext-lexical'
|
||||
|
||||
async function lexicalToHTML(
|
||||
editorData: SerializedEditorState,
|
||||
editorConfig: SanitizedEditorConfig,
|
||||
) {
|
||||
return await convertLexicalToHTML({
|
||||
converters: consolidateHTMLConverters({ editorConfig }),
|
||||
data: editorData,
|
||||
})
|
||||
}
|
||||
|
||||
await convertLexicalToHTML({
|
||||
converters: consolidateHTMLConverters({ editorConfig }),
|
||||
data: editorData,
|
||||
payload, // if you have payload but no req available, pass it in here to enable server-only functionality (e.g. proper conversion of upload nodes)
|
||||
req, // if you have req available, pass it in here to enable server-only functionality (e.g. proper conversion of upload nodes). No need to pass in payload if req is passed in.
|
||||
})
|
||||
```
|
||||
|
||||
This method employs `convertLexicalToHTML` from `@payloadcms/richtext-lexical`, which converts the serialized editor state into HTML.
|
||||
|
||||
Because every `Feature` is able to provide html converters, and because the `htmlFeature` can modify those or provide their own, we need to consolidate them with the default html Converters using the `consolidateHTMLConverters` function.
|
||||
|
||||
#### Example: Generating HTML within an afterRead hook
|
||||
|
||||
```ts
|
||||
import type { FieldHook } from 'payload'
|
||||
|
||||
import {
|
||||
HTMLConverterFeature,
|
||||
consolidateHTMLConverters,
|
||||
convertLexicalToHTML,
|
||||
defaultEditorConfig,
|
||||
defaultEditorFeatures,
|
||||
sanitizeServerEditorConfig,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
const hook: FieldHook = async ({ req, siblingData }) => {
|
||||
const editorConfig = defaultEditorConfig
|
||||
|
||||
editorConfig.features = [...defaultEditorFeatures, HTMLConverterFeature({})]
|
||||
|
||||
const sanitizedEditorConfig = await sanitizeServerEditorConfig(editorConfig, req.payload.config)
|
||||
|
||||
const html = await convertLexicalToHTML({
|
||||
converters: consolidateHTMLConverters({ editorConfig: sanitizedEditorConfig }),
|
||||
data: siblingData.lexicalSimple,
|
||||
req,
|
||||
})
|
||||
return html
|
||||
}
|
||||
```
|
||||
|
||||
### CSS
|
||||
|
||||
Payload's lexical HTML converter does not generate CSS for you, but it does add classes to the generated HTML. You can use these classes to style the HTML in your frontend.
|
||||
@@ -95,19 +116,33 @@ HTML Converters are typed as `HTMLConverter`, which contains the node type it sh
|
||||
import type { HTMLConverter } from '@payloadcms/richtext-lexical'
|
||||
|
||||
const UploadHTMLConverter: HTMLConverter<SerializedUploadNode> = {
|
||||
converter: async ({ node, payload }) => {
|
||||
const uploadDocument = await payload.findByID({
|
||||
id: node.value.id,
|
||||
collection: node.relationTo,
|
||||
})
|
||||
const url = (payload?.config?.serverURL || '') + uploadDocument?.url
|
||||
converter: async ({ node, req }) => {
|
||||
const uploadDocument: {
|
||||
value?: any
|
||||
} = {}
|
||||
if(req) {
|
||||
await populate({
|
||||
id,
|
||||
collectionSlug: node.relationTo,
|
||||
currentDepth: 0,
|
||||
data: uploadDocument,
|
||||
depth: 1,
|
||||
draft: false,
|
||||
key: 'value',
|
||||
overrideAccess: false,
|
||||
req,
|
||||
showHiddenFields: false,
|
||||
})
|
||||
}
|
||||
|
||||
if (!(uploadDocument?.mimeType as string)?.startsWith('image')) {
|
||||
const url = (req?.payload?.config?.serverURL || '') + uploadDocument?.value?.url
|
||||
|
||||
if (!(uploadDocument?.value?.mimeType as string)?.startsWith('image')) {
|
||||
// Only images can be serialized as HTML
|
||||
return ``
|
||||
}
|
||||
|
||||
return `<img src="${url}" alt="${uploadDocument?.filename}" width="${uploadDocument?.width}" height="${uploadDocument?.height}"/>`
|
||||
return `<img src="${url}" alt="${uploadDocument?.value?.filename}" width="${uploadDocument?.value?.width}" height="${uploadDocument?.value?.height}"/>`
|
||||
},
|
||||
nodeTypes: [UploadNode.getType()], // This is the type of the lexical node that this converter can handle. Instead of hardcoding 'upload' we can get the node type directly from the UploadNode, since it's static.
|
||||
}
|
||||
@@ -169,10 +204,11 @@ import { createHeadlessEditor } from '@lexical/headless' // <= make sure this pa
|
||||
import { getEnabledNodes, sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
|
||||
const yourEditorConfig // <= your editor config here
|
||||
const payloadConfig // <= your payload config here
|
||||
|
||||
const headlessEditor = createHeadlessEditor({
|
||||
nodes: getEnabledNodes({
|
||||
editorConfig: sanitizeServerEditorConfig(yourEditorConfig),
|
||||
editorConfig: sanitizeServerEditorConfig(yourEditorConfig, payloadConfig),
|
||||
}),
|
||||
})
|
||||
```
|
||||
@@ -200,7 +236,7 @@ yourEditorConfig.features = [
|
||||
If you have access to the sanitized collection config, you can get access to the lexical sanitized editor config & features, as every lexical richText field returns it. Here is an example how you can get it from another field's afterRead hook:
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig, RichTextField } from 'payload/types'
|
||||
import type { CollectionConfig, RichTextField } from 'payload'
|
||||
import { createHeadlessEditor } from '@lexical/headless'
|
||||
import type { LexicalRichTextAdapter, SanitizedServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
import {
|
||||
@@ -301,7 +337,7 @@ Convert markdown content to the Lexical editor format with the following:
|
||||
import { $convertFromMarkdownString } from '@lexical/markdown'
|
||||
import { sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
|
||||
const yourSanitizedEditorConfig = sanitizeServerEditorConfig(yourEditorConfig) // <= your editor config here
|
||||
const yourSanitizedEditorConfig = sanitizeServerEditorConfig(yourEditorConfig, payloadConfig) // <= your editor config & payload config here
|
||||
const markdown = `# Hello World`
|
||||
|
||||
headlessEditor.update(
|
||||
@@ -329,7 +365,7 @@ import { $convertToMarkdownString } from '@lexical/markdown'
|
||||
import { sanitizeServerEditorConfig } from '@payloadcms/richtext-lexical'
|
||||
import type { SerializedEditorState } from 'lexical'
|
||||
|
||||
const yourSanitizedEditorConfig = sanitizeServerEditorConfig(yourEditorConfig) // <= your editor config here
|
||||
const yourSanitizedEditorConfig = sanitizeServerEditorConfig(yourEditorConfig, payloadConfig) // <= your editor config & payload config here
|
||||
const yourEditorState: SerializedEditorState // <= your current editor state here
|
||||
|
||||
// Import editor state into your headless editor
|
||||
|
||||
@@ -10,6 +10,21 @@ keywords: lexical, rich text, editor, headless cms, migrate, migration
|
||||
|
||||
While both Slate and Lexical save the editor state in JSON, the structure of the JSON is different.
|
||||
|
||||
### Migration via Migration Script (Recommended)
|
||||
|
||||
Just import the `migrateSlateToLexical` function we provide, pass it the `payload` object and run it. Depending on the amount of collections, this might take a while.
|
||||
|
||||
IMPORTANT: This will overwrite all slate data. We recommend doing the following first:
|
||||
1. Take a backup of your entire database. If anything goes wrong and you do not have a backup, you are on your own and will not receive any support.
|
||||
2. Make every richText field a lexical editor. This script will only convert lexical richText fields with old Slate data
|
||||
3. Add the SlateToLexicalFeature (as seen below) first, and test it out by loading up the admin panel, to see if the migrator works as expected. You might have to build some custom converters for some fields first in order to convert custom Slate nodes. The SlateToLexicalFeature is where the converters are stored. Only fields with this feature added will be migrated.
|
||||
|
||||
```ts
|
||||
import { migrateSlateToLexical } from '@payloadcms/richtext-lexical'
|
||||
|
||||
await migrateSlateToLexical({ payload })
|
||||
```
|
||||
|
||||
### Migration via SlateToLexicalFeature
|
||||
|
||||
One way to handle this is to just give your lexical editor the ability to read the slate JSON.
|
||||
@@ -17,7 +32,7 @@ One way to handle this is to just give your lexical editor the ability to read t
|
||||
Simply add the `SlateToLexicalFeature` to your editor:
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { SlateToLexicalFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
@@ -42,14 +57,14 @@ This is by far the easiest way to migrate from Slate to Lexical, although it doe
|
||||
- There is a performance hit when initializing the lexical editor
|
||||
- The editor will still output the Slate data in the output JSON, as the on-the-fly converter only runs for the admin panel
|
||||
|
||||
The easy way to solve this: Just save the document! This overrides the slate data with the lexical data, and the next time the document is loaded, the lexical data will be used. This solves both the performance and the output issue for that specific document.
|
||||
The easy way to solve this: Edit the richText field and save the document! This overrides the slate data with the lexical data, and the next time the document is loaded, the lexical data will be used. This solves both the performance and the output issue for that specific document. This, however, is a slow and gradual migration process, thus you will have to support both API formats. Especially for a large number of documents, we recommend running the migration script, as explained above.
|
||||
|
||||
### Migration via migration script
|
||||
|
||||
The method described above does not solve the issue for all documents, though. If you want to convert all your documents to lexical, you can use a migration script. Here's a simple example:
|
||||
|
||||
```ts
|
||||
import type { Payload } from 'payload'
|
||||
import type { Payload, YourDocumentType } from 'payload'
|
||||
import type { YourDocumentType } from 'payload/generated-types'
|
||||
|
||||
import {
|
||||
@@ -146,7 +161,7 @@ When using a migration script, you can add your custom converters to the `conver
|
||||
When using the `SlateToLexicalFeature`, you can add your custom converters to the `converters` property of the `SlateToLexicalFeature` props:
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import {
|
||||
SlateToLexicalFeature,
|
||||
|
||||
@@ -32,7 +32,7 @@ npm install @payloadcms/richtext-lexical
|
||||
Once you have it installed, you can pass it to your top-level Payload config as follows:
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export default buildConfig({
|
||||
@@ -47,7 +47,7 @@ export default buildConfig({
|
||||
You can also override Lexical settings on a field-by-field basis as follows:
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
@@ -89,7 +89,7 @@ import { CallToAction } from '../blocks/CallToAction'
|
||||
|
||||
{
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
features: ({ defaultFeatures, rootFeatures }) => [
|
||||
...defaultFeatures,
|
||||
LinkFeature({
|
||||
// Example showing how to customize the built-in fields
|
||||
@@ -134,6 +134,15 @@ import { CallToAction } from '../blocks/CallToAction'
|
||||
}
|
||||
```
|
||||
|
||||
`features` can be both an array of features, or a function returning an array of features. The function provides the following props:
|
||||
|
||||
|
||||
| Prop | Description |
|
||||
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`defaultFeatures`** | This opinionated array contains all "recommended" default features. You can see which features are included in the default features in the table below. |
|
||||
| **`rootFeatures`** | This array contains all features that are enabled in the root richText editor (the one defined in the payload.config.ts). If this field is the root richText editor, or if the root richText editor is not a lexical editor, this array will be empty. |
|
||||
|
||||
|
||||
## Features overview
|
||||
|
||||
Here's an overview of all the included features:
|
||||
@@ -168,12 +177,78 @@ Notice how even the toolbars are features? That's how extensible our lexical edi
|
||||
|
||||
## Creating your own, custom Feature
|
||||
|
||||
Creating your own custom feature requires deep knowledge of the Lexical editor. We recommend you take a look at the [Lexical documentation](https://lexical.dev/docs/intro) first - especially the "concepts" section.
|
||||
You can find more information about creating your own feature in our [building custom feature docs](lexical/building-custom-features).
|
||||
|
||||
Next, take a look at the [features we've already built](https://github.com/payloadcms/payload/tree/main/packages/richtext-lexical/src/field/features) - understanding how they work will help you understand how to create your own. There is no difference between the features included by default and the ones you create yourself - since those features are all isolated from the "core", you have access to the same APIs, whether the feature is part of payload or not!
|
||||
## TypeScript
|
||||
|
||||
## Coming Soon
|
||||
Every single piece of saved data is 100% fully-typed within lexical. It provides a type for every single node, which can be imported from `@payloadcms/richtext-lexical` - each type is prefixed with `Serialized`, e.g. `SerializedUploadNode`.
|
||||
|
||||
Lots more documentation will be coming soon, which will show in detail how to create your own custom features within Lexical.
|
||||
In order to fully type the entire editor JSON, you can use our `TypedEditorState` helper type, which accepts a union of all possible node types as a generic. The reason we do not provide a type which already contains all possible node types is because the possible node types depend on which features you have enabled in your editor. Here is an example:
|
||||
|
||||
For now, take a look at the TypeScript interfaces and let us know if you need a hand. Much more will be coming from the Payload team on this topic soon.
|
||||
```ts
|
||||
import type {
|
||||
SerializedAutoLinkNode,
|
||||
SerializedBlockNode,
|
||||
SerializedHorizontalRuleNode,
|
||||
SerializedLinkNode,
|
||||
SerializedListItemNode,
|
||||
SerializedListNode,
|
||||
SerializedParagraphNode,
|
||||
SerializedQuoteNode,
|
||||
SerializedRelationshipNode,
|
||||
SerializedTextNode,
|
||||
SerializedUploadNode,
|
||||
TypedEditorState,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
const editorState: TypedEditorState<
|
||||
| SerializedAutoLinkNode
|
||||
| SerializedBlockNode
|
||||
| SerializedHorizontalRuleNode
|
||||
| SerializedLinkNode
|
||||
| SerializedListItemNode
|
||||
| SerializedListNode
|
||||
| SerializedParagraphNode
|
||||
| SerializedQuoteNode
|
||||
| SerializedRelationshipNode
|
||||
| SerializedTextNode
|
||||
| SerializedUploadNode
|
||||
> = {
|
||||
root: {
|
||||
type: 'root',
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: 'Some text. Every property here is fully-typed',
|
||||
type: 'text',
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
textFormat: 0,
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This is a type-safe representation of the editor state. Looking at the auto-suggestions of `type` it will show you all the possible node types you can use.
|
||||
|
||||
Make sure to only use types exported from `@payloadcms/richtext-lexical`, not from the lexical core packages. We only have control over types we export and can guarantee that those are correct, even though lexical core may export types with identical names.
|
||||
|
||||
### Automatic type generation
|
||||
|
||||
Lexical does not generate the accurate type definitions for your richText fields for you yet - this will be improved in the future. Currently, it only outputs the rough shape of the editor JSON which you can enhance using type assertions.
|
||||
|
||||
@@ -40,8 +40,9 @@ import config from '../payload.config'
|
||||
export default async function Page() {
|
||||
const payload = await getPayloadHMR({ config })
|
||||
|
||||
const page = await payload.find({
|
||||
const page = await payload.findByID({
|
||||
collection: 'pages',
|
||||
id: '123',
|
||||
draft: true
|
||||
})
|
||||
|
||||
|
||||
76
docs/local-api/outside-nextjs.mdx
Normal file
76
docs/local-api/outside-nextjs.mdx
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Using Payload outside Next.js
|
||||
label: Outside Next.js
|
||||
order: 20
|
||||
desc: Payload can be used outside of Next.js within standalone scripts or in other frameworks like Remix, Sveltekit, Nuxt, and similar.
|
||||
keywords: local api, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, express
|
||||
---
|
||||
|
||||
Payload can be used completely outside of Next.js which is helpful in cases like running scripts, using Payload in a separate backend service, or using Payload's Local API to fetch your data directly from your database in other frontend frameworks like Sveltekit, Remix, Nuxt, and similar.
|
||||
|
||||
<Banner>
|
||||
<strong>Note:</strong>
|
||||
<br/>
|
||||
Payload and all of its official packages are fully ESM. If you want to use Payload within your own projects, make sure you are writing your scripts in ESM format or dynamically importing the Payload config.
|
||||
</Banner>
|
||||
|
||||
## Importing the Payload config outside of Next.js
|
||||
|
||||
Your Payload config likely has imports which need to be handled properly, such as CSS imports and similar. If you were to try and import your config without any Node support for SCSS / CSS files, you'll see errors that arise accordingly.
|
||||
|
||||
This is especially relevant if you are importing your Payload config outside of a bundler context, such as in standalone Node scripts.
|
||||
|
||||
For these cases, you can use Payload's `importConfig` function to handle importing your config safely. It will handle everything you need to be able to load and use your Payload config, without any client-side files present.
|
||||
|
||||
Here's an example of a seed script that creates a few documents for local development / testing purposes, using Payload's `importConfig` function to safely import Payload, and the `getPayload` function to retrieve an initialized copy of Payload.
|
||||
|
||||
```ts
|
||||
// We are importing `getPayload` because we don't need HMR
|
||||
// for a standalone script. For usage of Payload inside Next.js,
|
||||
// you should always use `import { getPayloadHMR } from '@payloadcms/next/utilities'` instead.
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
// This is a helper function that will make sure we can safely load the Payload config
|
||||
// and all of its client-side files, such as CSS, SCSS, etc.
|
||||
import { importConfig } from 'payload/node'
|
||||
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
// In ESM, you can create the "dirname" variable
|
||||
// like this. We'll use this with `dotenv` to load our `.env` file, if necessary.
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
// If you don't need to load your .env file,
|
||||
// then you can skip this part!
|
||||
dotenv.config({
|
||||
path: path.resolve(dirname, '../.env'),
|
||||
})
|
||||
|
||||
const seed = async () => {
|
||||
// Get a local copy of Payload by passing your config
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
const user = await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'dev@payloadcms.com',
|
||||
password: 'some-password'
|
||||
}
|
||||
})
|
||||
|
||||
const page = await payload.create({
|
||||
collection: 'pages',
|
||||
data: {
|
||||
title: 'My Homepage',
|
||||
// other data to seed here
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Call the function here to run your seed script
|
||||
seed()
|
||||
|
||||
```
|
||||
@@ -6,53 +6,29 @@ desc: The Payload Local API allows you to interact with your database and execut
|
||||
keywords: local api, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
The Payload Local API gives you the ability to execute the same operations that are available through REST and GraphQL
|
||||
within Node, directly on your server. Here, you don't need to deal with server latency or network speed whatsoever and
|
||||
can interact directly with your database.
|
||||
The Payload Local API gives you the ability to execute the same operations that are available through REST and GraphQL within Node, directly on your server. Here, you don't need to deal with server latency or network speed whatsoever and can interact directly with your database.
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />
|
||||
The Local API is incredibly powerful when used with server-side rendering app frameworks like
|
||||
NextJS. With other headless CMS, you need to request your data from third-party servers which can
|
||||
add significant loading time to your server-rendered pages. With Payload, you don't have to leave
|
||||
your server to gather the data you need. It can be incredibly fast and is definitely a game
|
||||
changer.
|
||||
The Local API is incredibly powerful when used in React Server Components and other similar server-side contexts. With other headless CMS, you need to request your data from third-party servers via an HTTP layer, which can add significant loading time to your server-rendered pages. With Payload, you don't have to leave your server to gather the data you need. It can be incredibly fast and is definitely a game changer.
|
||||
</Banner>
|
||||
|
||||
Here are some common examples of how you can use the Local API:
|
||||
|
||||
- Fetching Payload data within React Server Components
|
||||
- Seeding data via Node seed scripts that you write and maintain
|
||||
- Opening custom routes which feature additional functionality but still rely on Payload
|
||||
- Opening custom Next.js route handlers which feature additional functionality but still rely on Payload
|
||||
- Within Payload access control and hook functions
|
||||
- Within access control and hook functions
|
||||
|
||||
## Accessing payload
|
||||
## Accessing Payload
|
||||
|
||||
You can gain access to the currently running `payload` object via two ways:
|
||||
|
||||
#### Importing it
|
||||
#### Accessing from args or `req`
|
||||
|
||||
You can import or require `payload` into your own files after it's been initialized, but you need to make sure that
|
||||
your `import` / `require` statements come **after** you call `payload.init()`—otherwise Payload won't have been
|
||||
initialized yet. That might be obvious. To us, it's usually not.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
import payload from 'payload'
|
||||
import { CollectionAfterChangeHook } from 'payload/types'
|
||||
|
||||
const afterChangeHook: CollectionAfterChangeHook = async () => {
|
||||
const posts = await payload.find({
|
||||
collection: 'posts',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### Accessing from the `req`
|
||||
|
||||
Payload is available anywhere you have access to the `req` - including within your access control and hook
|
||||
functions.
|
||||
In most places within Payload itself, you can access `payload` directly from the arguments of hooks, access control, validation functions, and similar. This is the simplest way to access Payload in most cases. Most config functions take the `req` (Request) object, which has Payload bound to it (`req.payload`).
|
||||
|
||||
Example:
|
||||
|
||||
@@ -64,22 +40,58 @@ const afterChangeHook: CollectionAfterChangeHook = async ({ req: { payload } })
|
||||
}
|
||||
```
|
||||
|
||||
#### Importing it
|
||||
|
||||
If you want to import Payload in places where you don't have the option to access it from function arguments or `req`, you can import it and initialize it.
|
||||
|
||||
There are two places to import Payload:
|
||||
|
||||
**Option 1 - using HMR, within Next.js**
|
||||
|
||||
```ts
|
||||
import { getPayloadHMR } from '@payloadcms/next/utilities'
|
||||
import config from '@payload-config'
|
||||
|
||||
const payload = await getPayloadHMR({ config })
|
||||
```
|
||||
|
||||
You should import Payload using the first option (`getPayloadHMR`) if you are using Payload inside of Next.js (like route handlers, server components, and similar.)
|
||||
|
||||
This way, in Next.js development mode, Payload will work with Hot Module Replacement (HMR), and as you make changes to your Payload config, your usage of Payload will always be in sync with your changes. In production, `getPayloadHMR` simply disables all HMR functionality so you don't need to write your code any differently. We handle optimization for you in production mode.
|
||||
|
||||
If you are accessing Payload via function arguments or `req.payload`, HMR is automatically supported if you are using it within Next.js.
|
||||
|
||||
**Option 2 - outside of Next.js**
|
||||
|
||||
If you are using Payload outside of Next.js, for example in standalone scripts or in other frameworks, you can import Payload with no HMR functionality.
|
||||
|
||||
```ts
|
||||
import { getPayload } from 'payload'
|
||||
import { importConfig } from 'payload/node'
|
||||
|
||||
const config = await importConfig('./payload.config.ts')
|
||||
const payload = await getPayload({ config })
|
||||
```
|
||||
|
||||
Both options function in exactly the same way outside of one having HMR support and the other not. However, when you import your Payload config, you need to make sure that you can import it safely.
|
||||
|
||||
For more information about using Payload outside of Next.js, [click here](/docs/beta/local-api/outside-nextjs).
|
||||
|
||||
## Local options available
|
||||
|
||||
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are
|
||||
executed in.
|
||||
You can specify more options within the Local API vs. REST or GraphQL due to the server-only context that they are executed in.
|
||||
|
||||
| Local Option | Description |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
|
||||
| `data` | The data to use within the operation. Required for `create`, `update`. |
|
||||
| `depth` | [Control auto-population](/docs/getting-started/concepts#depth) of nested relationship and upload fields. |
|
||||
| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
|
||||
| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
|
||||
| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
|
||||
| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
|
||||
| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
|
||||
| `pagination` | Set to false to return all documents and avoid querying for document counts. |
|
||||
| Local Option | Description |
|
||||
| ------------------ | ------------ |
|
||||
| `collection` | Required for Collection operations. Specifies the Collection slug to operate against. |
|
||||
| `data` | The data to use within the operation. Required for `create`, `update`. |
|
||||
| `depth` | [Control auto-population](/docs/getting-started/concepts#depth) of nested relationship and upload fields. |
|
||||
| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
|
||||
| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
|
||||
| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
|
||||
| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
|
||||
| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
|
||||
| `pagination` | Set to false to return all documents and avoid querying for document counts. |
|
||||
| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
|
||||
|
||||
_There are more options available on an operation by operation basis outlined below._
|
||||
@@ -424,109 +436,6 @@ const result = await payload.updateGlobal({
|
||||
})
|
||||
```
|
||||
|
||||
## Next.js Conflict with Local API
|
||||
|
||||
There is a known issue when using the Local API with Next.js version `13.4.13` and higher. Next.js executes within a
|
||||
separate child process, and Payload has not been initialized yet in these instances. That means that unless you
|
||||
explicitly initialize Payload within your operation, it will not be running and return no data / an empty object.
|
||||
|
||||
As a workaround, we recommend leveraging the following pattern to determine and ensure Payload is initialized:
|
||||
|
||||
```
|
||||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
import type { Payload } from 'payload'
|
||||
import payload from 'payload'
|
||||
import type { InitOptions } from 'payload/config'
|
||||
import { seed as seedData } from './seed'
|
||||
|
||||
dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
let cached = (global as any).payload
|
||||
|
||||
if (!cached) {
|
||||
cached = (global as any).payload = { client: null, promise: null }
|
||||
}
|
||||
|
||||
interface Args {
|
||||
initOptions?: Partial<InitOptions>
|
||||
seed?: boolean
|
||||
}
|
||||
|
||||
export const getPayloadClient = async ({ initOptions, seed }: Args = {}): Promise<Payload> => {
|
||||
if (!process.env.DATABASE_URI) {
|
||||
throw new Error('DATABASE_URI environment variable is missing')
|
||||
}
|
||||
if (!process.env.PAYLOAD_SECRET) {
|
||||
throw new Error('PAYLOAD_SECRET environment variable is missing')
|
||||
}
|
||||
if (cached.client) {
|
||||
return cached.client
|
||||
}
|
||||
if (!cached.promise) {
|
||||
cached.promise = payload.init({
|
||||
mongoURL: process.env.DATABASE_URI,
|
||||
secret: process.env.PAYLOAD_SECRET,
|
||||
local: initOptions?.express ? false : true,
|
||||
...(initOptions || {}),
|
||||
})
|
||||
}
|
||||
try {
|
||||
process.env.PAYLOAD_DROP_DATABASE = seed ? 'true' : 'false'
|
||||
cached.client = await cached.promise
|
||||
if (seed) {
|
||||
payload.logger.info('---- SEEDING DATABASE ----')
|
||||
await seedData(payload)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
cached.promise = null
|
||||
throw e
|
||||
}
|
||||
return cached.client
|
||||
}
|
||||
```
|
||||
|
||||
To checkout how this works in a project, take a look at
|
||||
our [custom server example](https://github.com/payloadcms/payload/blob/master/examples/custom-server/src/getPayload.ts).
|
||||
|
||||
## Example Script using Local API
|
||||
|
||||
The Local API is especially useful for running scripts
|
||||
|
||||
```ts
|
||||
import payload from 'payload'
|
||||
import path from 'path'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config({
|
||||
path: path.resolve(__dirname, '../.env'),
|
||||
})
|
||||
|
||||
const { PAYLOAD_SECRET } = process.env
|
||||
|
||||
const doAction = async (): Promise<void> => {
|
||||
await payload.init({
|
||||
secret: PAYLOAD_SECRET,
|
||||
local: true, // Enables local mode, doesn't spin up a server or frontend
|
||||
})
|
||||
|
||||
// Perform any Local API operations here
|
||||
await payload.find({
|
||||
collection: 'posts',
|
||||
// where: {} // optional
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'posts',
|
||||
data: {},
|
||||
})
|
||||
}
|
||||
|
||||
doAction()
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
Local API calls will automatically infer your [generated types](/docs/typescript/generating-types).
|
||||
|
||||
@@ -33,6 +33,7 @@ Instead for any global styles you can:
|
||||
|
||||
```tsx
|
||||
// payload.config.js
|
||||
import { MyProvider } from './providers/MyProvider'
|
||||
|
||||
//...
|
||||
admin: {
|
||||
@@ -44,20 +45,18 @@ admin: {
|
||||
},
|
||||
//...
|
||||
|
||||
// MyProvider.tsx
|
||||
// providers/MyProvider.tsx
|
||||
|
||||
import React from 'react'
|
||||
import './globals.css'
|
||||
|
||||
const MyProvider: React.FC<{children?: any}= ({ children }) ={
|
||||
export const MyProvider: React.FC<{children?: any}= ({ children }) ={
|
||||
return (
|
||||
<React.fragment>
|
||||
{children}
|
||||
</React.fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Provider
|
||||
```
|
||||
|
||||
4. The `admin.indexHTML` property has been removed.
|
||||
@@ -65,9 +64,51 @@ export default Provider
|
||||
|
||||
Instead, use the new `beforeDuplicate` field-level hook which take the usual field hook arguments.
|
||||
|
||||
```tsx
|
||||
// ❌ Before
|
||||
// file: collections/Posts.ts
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const PostsCollection: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
admin: {
|
||||
hooks: {
|
||||
beforeDuplicate: ({ data }) => {
|
||||
return {
|
||||
...data,
|
||||
title: `${data.title}-duplicate`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// TODO: add snippet here of old vs new
|
||||
// ✅ After
|
||||
// file: collections/Posts.ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const PostsCollection: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
beforeDuplicate: [
|
||||
({ data }) => `${data.title}-duplicate`
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
6. Import all Payload React components via the `@payloadcms/ui` package instead of `payload`:
|
||||
@@ -78,9 +119,254 @@ If you were previously importing components into your app from the `payload` pac
|
||||
- Migrate your component (if necessary, see next bullet)
|
||||
|
||||
```tsx
|
||||
import { Button } from '@payloadcms/ui/elements/Button'
|
||||
import {
|
||||
// Form Fields
|
||||
ArrayField,
|
||||
BlocksField,
|
||||
CheckboxField,
|
||||
CodeField,
|
||||
CollapsibleField,
|
||||
ConfirmPasswordField,
|
||||
DateTimeField,
|
||||
EmailField,
|
||||
GroupField,
|
||||
HiddenField,
|
||||
JSONField,
|
||||
NumberField,
|
||||
PasswordField,
|
||||
PointField,
|
||||
RadioGroupField,
|
||||
RelationshipField,
|
||||
RichTextField,
|
||||
RowField,
|
||||
SelectField,
|
||||
TabsField,
|
||||
TextField,
|
||||
TextareaField,
|
||||
UIField,
|
||||
Upload,
|
||||
UploadField,
|
||||
|
||||
// TODO: Add new full list of exports
|
||||
// Input Elements
|
||||
HiddenInput,
|
||||
TextInput,
|
||||
TextareaInput,
|
||||
UploadInput,
|
||||
|
||||
// Hooks
|
||||
useActions,
|
||||
useAddClientFunction,
|
||||
useAllFormFields,
|
||||
useAuth,
|
||||
useClientFunctions,
|
||||
useComponentMap,
|
||||
useConfig,
|
||||
useDebounce,
|
||||
useDebouncedCallback,
|
||||
useDebouncedEffect,
|
||||
useDelay,
|
||||
useDelayedRender,
|
||||
useDocumentDrawer,
|
||||
useDocumentEvents,
|
||||
useDocumentInfo,
|
||||
useDrawerSlug,
|
||||
useEditDepth,
|
||||
useEntityVisibility,
|
||||
useField,
|
||||
useFieldComponents,
|
||||
useFieldProps,
|
||||
useForm,
|
||||
useFormFields,
|
||||
useFormInitializing,
|
||||
useFormModified,
|
||||
useFormProcessing,
|
||||
useFormQueryParams,
|
||||
useFormSubmitted,
|
||||
useHotkey,
|
||||
useIntersect,
|
||||
useListDrawer,
|
||||
useListInfo,
|
||||
useListQuery,
|
||||
useLocale,
|
||||
useModal,
|
||||
useNav,
|
||||
useOperation,
|
||||
useParams,
|
||||
usePayloadAPI,
|
||||
usePreferences,
|
||||
useResize,
|
||||
useRouteCache,
|
||||
useRowLabel,
|
||||
useScrollInfo,
|
||||
useSearchParams,
|
||||
useSelection,
|
||||
useStepNav,
|
||||
useTableCell,
|
||||
useTheme,
|
||||
useThrottledEffect,
|
||||
useTranslation,
|
||||
useUseTitleField,
|
||||
useWatchForm,
|
||||
useWindowInfo,
|
||||
|
||||
// Providers
|
||||
ActionsProvider,
|
||||
AuthProvider,
|
||||
ClientFunctionProvider,
|
||||
ComponentMapProvider,
|
||||
ConfigProvider,
|
||||
DocumentEventsProvider,
|
||||
DocumentInfoProvider,
|
||||
EditDepthProvider,
|
||||
EntityVisibilityProvider,
|
||||
FieldComponentsProvider,
|
||||
FieldPropsProvider,
|
||||
FormQueryParamsProvider,
|
||||
ListInfoProvider,
|
||||
ListQueryProvider,
|
||||
LocaleProvider,
|
||||
OperationProvider,
|
||||
ParamsProvider,
|
||||
PreferencesProvider,
|
||||
RelationshipProvider,
|
||||
RootProvider,
|
||||
RouteCacheProvider,
|
||||
RowLabelProvider,
|
||||
ScrollInfoProvider,
|
||||
SearchParamsProvider,
|
||||
SelectionProvider,
|
||||
TableCellProvider,
|
||||
TableColumnsProvider,
|
||||
ThemeProvider,
|
||||
TranslationProvider,
|
||||
WindowInfoProvider,
|
||||
|
||||
// View Components
|
||||
Account,
|
||||
Logout,
|
||||
|
||||
// Elements
|
||||
Button,
|
||||
Card,
|
||||
Collapsible,
|
||||
CopyToClipboard,
|
||||
DefaultPublishButton,
|
||||
Drawer,
|
||||
DrawerToggler,
|
||||
ErrorPill,
|
||||
Gutter,
|
||||
Hamburger,
|
||||
LoadingOverlay,
|
||||
LoadingOverlayToggle,
|
||||
Modal,
|
||||
NavGroup,
|
||||
NavToggler,
|
||||
Pagination,
|
||||
PerPage,
|
||||
Pill,
|
||||
Popup,
|
||||
PopupList,
|
||||
PublishButton,
|
||||
RenderTitle,
|
||||
RowLabel,
|
||||
ShimmerEffect,
|
||||
StaggeredShimmers,
|
||||
Table,
|
||||
Thumbnail,
|
||||
Tooltip,
|
||||
Translation,
|
||||
|
||||
// Icons
|
||||
CalendarIcon,
|
||||
CheckIcon,
|
||||
ChevronIcon,
|
||||
CloseMenuIcon,
|
||||
CodeBlockIcon,
|
||||
CopyIcon,
|
||||
DragHandleIcon,
|
||||
EditIcon,
|
||||
LineIcon,
|
||||
LinkIcon,
|
||||
LogOutIcon,
|
||||
MenuIcon,
|
||||
MinimizeMaximizeIcon,
|
||||
MoreIcon,
|
||||
PlusIcon,
|
||||
SearchIcon,
|
||||
SwapIcon,
|
||||
XIcon,
|
||||
|
||||
// Constant Variables
|
||||
defaultTheme,
|
||||
fieldBaseClass,
|
||||
|
||||
// TS Types
|
||||
ActionMap,
|
||||
CollectionComponentMap,
|
||||
ColumnPreferences,
|
||||
ConfigComponentMapBase,
|
||||
DocumentInfoContext,
|
||||
DocumentInfoProps,
|
||||
FieldType,
|
||||
FieldComponentProps,
|
||||
FormProps,
|
||||
RowLabelProps,
|
||||
SelectFieldProps,
|
||||
TabsFieldProps,
|
||||
TextAreaInputProps,
|
||||
TextFieldProps,
|
||||
TextInputProps,
|
||||
TextareaFieldProps,
|
||||
Theme,
|
||||
UploadFieldProps,
|
||||
UploadInputProps,
|
||||
|
||||
// Other Exports
|
||||
AppHeader,
|
||||
BlocksDrawer,
|
||||
Column,
|
||||
ComponentMap,
|
||||
DefaultBlockImage,
|
||||
DeleteMany,
|
||||
DocumentControls,
|
||||
DocumentFields,
|
||||
EditMany,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldLabel,
|
||||
FieldMap,
|
||||
File,
|
||||
Form,
|
||||
FormFieldBase,
|
||||
FormLoadingOverlayToggle,
|
||||
FormSubmit,
|
||||
GenerateConfirmation,
|
||||
GlobalComponentMap,
|
||||
HydrateClientUser,
|
||||
ListControls,
|
||||
ListSelection,
|
||||
MappedField,
|
||||
MappedTab,
|
||||
NullifyLocaleField,
|
||||
Options,
|
||||
PublishMany,
|
||||
ReactSelect,
|
||||
ReactSelectOption,
|
||||
ReducedBlock,
|
||||
RenderFields,
|
||||
SectionTitle,
|
||||
Select,
|
||||
SetStepNav,
|
||||
SetViewActions,
|
||||
SortColumn,
|
||||
StepNavItem,
|
||||
UnpublishMany,
|
||||
ViewDescription,
|
||||
WatchChildErrors,
|
||||
formatDrawerSlug,
|
||||
toast,
|
||||
withCondition,
|
||||
} from '@payloadcms/ui'
|
||||
```
|
||||
|
||||
7. Migrate all Custom Components to Server Components.
|
||||
@@ -88,18 +374,43 @@ import { Button } from '@payloadcms/ui/elements/Button'
|
||||
All Custom Components are now server-rendered, and therefore, cannot use state or hooks directly. If you’re using Custom Components in your app that requires state or hooks, define your component in a separate file with the `'use client'` directive at the top, then render *that* client component within your server component. Remember you can only pass serializable props to this component, so props cannot be blindly spread.
|
||||
|
||||
```tsx
|
||||
// file: components/MyServerComponent.tsx
|
||||
import React from 'react'
|
||||
import type { ServerOnlyProps } from './types.ts'
|
||||
import MyClientComponent from './client.tsx'
|
||||
import { MyClientComponent } from './MyClientComponent.tsx'
|
||||
|
||||
export const MyServerComponent: React.FC<ServerOnlyProps= (serverOnlyProps) ={
|
||||
const clientOnlyProps = {
|
||||
// ... sanitize server-only props here as needed
|
||||
}
|
||||
const clientOnlyProps = {
|
||||
// ... sanitize server-only props here as needed
|
||||
}
|
||||
|
||||
return (
|
||||
<MyClientComponent {...clientOnlyProps} />
|
||||
)
|
||||
return (
|
||||
<MyClientComponent {...clientOnlyProps} />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// file: components/MyClientComponent.tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
|
||||
export const MyClientComponent = {
|
||||
const [count, setCount] = React.useState(0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>This component uses state, it MUST be a client component</h1>
|
||||
<button
|
||||
type="button"
|
||||
onClick(() => {
|
||||
setCount(prevCount => prevCount + 1)
|
||||
})
|
||||
>
|
||||
Increment
|
||||
</button>
|
||||
<span>Count: {count}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -109,9 +420,10 @@ Use `admin.custom` properties will be available in both server and client bundle
|
||||
|
||||
|
||||
```tsx
|
||||
// payload.config.ts
|
||||
// file: payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
buildConfig({
|
||||
export default buildConfig({
|
||||
// Server Only
|
||||
custom: {
|
||||
someProperty: 'value'
|
||||
@@ -130,18 +442,52 @@ This is because we cannot pass your `description` function to the client for exe
|
||||
|
||||
|
||||
```tsx
|
||||
// TODO: add config snippet for total clarity
|
||||
// file: payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
import { ServerRenderedDescription } from './components/ServerRenderedDescription.tsx'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
collections: [
|
||||
{
|
||||
slug: 'posts',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
admin: {
|
||||
description: ServerRenderedDescription
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
// ...
|
||||
})
|
||||
|
||||
|
||||
// file: components/ServerRenderedDescription.tsx
|
||||
import React from 'react'
|
||||
// TODO: get rest of imports
|
||||
|
||||
export const CustomFieldDescriptionComponent: DescriptionComponent = () ={
|
||||
import { ClientRenderedDescription } from './ClientRenderedDescription.tsx'
|
||||
|
||||
export const ServerRenderedDescription = () => <ClientRenderedDescription />
|
||||
|
||||
|
||||
// file: components/ClientRenderedDescription.tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import type { DescriptionComponent } from 'payload'
|
||||
import { useFieldProps, useFormFields } from '@payloadcms/ui'
|
||||
|
||||
export const ClientRenderedDescription: DescriptionComponent = () ={
|
||||
const { path } = useFieldProps()
|
||||
const { value } = useFormFields(([fields]) =fields[path])
|
||||
const { value } = useFormFields(([fields]) => fields[path])
|
||||
const customDescription = `Component description: ${path} - ${value}`
|
||||
|
||||
return (
|
||||
<div>
|
||||
{`Component description: ${path} - ${value}`}
|
||||
{customDescription}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -153,19 +499,51 @@ This is because we cannot pass your `label` function to the client for execution
|
||||
|
||||
|
||||
```tsx
|
||||
// TODO: add config snippet for total clarity
|
||||
// file: payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
import { ServerRenderedCollapsibleLabel } from './components/ServerRenderedCollapsibleLabel.tsx'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
collections: [
|
||||
{
|
||||
slug: 'posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: ServerRenderedCollapsibleLabel
|
||||
fields: [
|
||||
{
|
||||
name: 'fieldInCollapsible',
|
||||
type: 'text',
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
// ...
|
||||
})
|
||||
|
||||
|
||||
// file: components/ServerRenderedCollapsibleLabel.tsx
|
||||
import React from 'react'
|
||||
// TODO: get rest of imports
|
||||
import { ClientCollapsibleLabel } from './ClientCollapsibleLabel.tsx'
|
||||
|
||||
export const CustomFieldLabelComponent: LabelComponent = () => {
|
||||
export const ServerRenderedCollapsibleLabel = () => <ClientCollapsibleLabel />
|
||||
|
||||
|
||||
// file: components/ClientCollapsibleLabel.tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useFieldProps, useFormFields } from '@payloadcms/ui'
|
||||
|
||||
export const ClientCollapsibleLabel = () => {
|
||||
const { path } = useFieldProps()
|
||||
const { value } = useFormFields(([fields]) =fields[path])
|
||||
const { value } = useFormFields(([fields]) => fields[path])
|
||||
const customLabel = `${value?.fieldInCollapsible || 'Untitled Collapsible'}`
|
||||
|
||||
return (
|
||||
<div>
|
||||
{`Component label: ${path} - ${value}`}
|
||||
</div>
|
||||
)
|
||||
return <div>{customLabel}</div>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -175,17 +553,52 @@ If using a custom component, you must now get the data yourself via the `useTabl
|
||||
|
||||
|
||||
```tsx
|
||||
// TODO: add config snippet for total clarity
|
||||
// file: payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
import { ServerRenderedTitleCell } from './components/ServerRenderedTitleCell.tsx'
|
||||
|
||||
export default buildConfig({
|
||||
// ...
|
||||
collections: [
|
||||
{
|
||||
slug: 'posts',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Cell: ServerRenderedTitleCell
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
// ...
|
||||
})
|
||||
|
||||
|
||||
// file: components/ServerRenderedTitleCell.tsx
|
||||
import React from 'react'
|
||||
// TODO: get rest of imports
|
||||
|
||||
export const CustomCellComponent: CellComponent = () ={
|
||||
import { ClientTitleCell } from './ClientTitleCell.tsx'
|
||||
|
||||
export const ServerRenderedTitleCell = () => <ClientTitleCell />
|
||||
|
||||
|
||||
// file: components/ClientTitleCell.tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useTableCell } from '@payloadcms/ui'
|
||||
|
||||
export const ClientTitleCell: CellComponent = () ={
|
||||
const { cellData, rowData } = useTableCell()
|
||||
const customCellText = `Component cell: ${cellData}`
|
||||
|
||||
return (
|
||||
<div>
|
||||
{`Component cell: ${cellData}`}
|
||||
{customCellText}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -194,48 +607,54 @@ export const CustomCellComponent: CellComponent = () ={
|
||||
12. `admin.components.RowLabel` no longer accepts a function, instead use a custom component and make use of the `useRowLabel` hook:
|
||||
|
||||
```tsx
|
||||
// ❌ Before
|
||||
// file: payload.config.ts
|
||||
import { buildConfig } from 'payload'
|
||||
import { ServerRenderedArrayRowLabel } from './components/ServerRenderedArrayRowLabel.tsx'
|
||||
|
||||
// Field config
|
||||
{
|
||||
type: 'array',
|
||||
admin: {
|
||||
components: {
|
||||
RowLabel: ({ data }) ={
|
||||
console.log(data)
|
||||
return data?.title || 'Untitled'
|
||||
},
|
||||
export default buildConfig({
|
||||
// ...
|
||||
collections: [
|
||||
{
|
||||
slug: 'posts',
|
||||
fields: [
|
||||
{
|
||||
name: 'people',
|
||||
type: 'array',
|
||||
admin: {
|
||||
components: {
|
||||
RowLabel: ServerRenderedArrayRowLabel
|
||||
}
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
]
|
||||
// ...
|
||||
})
|
||||
|
||||
```tsx
|
||||
// ✅ After
|
||||
|
||||
// Field config
|
||||
{
|
||||
type: 'array',
|
||||
admin: {
|
||||
components: {
|
||||
RowLabel: ArrayRowLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Component
|
||||
'use client'
|
||||
|
||||
import type { RowLabelComponent } from 'payload/types'
|
||||
|
||||
import { useRowLabel } from '@payloadcms/ui/forms/RowLabel/Context'
|
||||
// file: components/ServerRenderedArrayRowLabel.tsx
|
||||
import React from 'react'
|
||||
import { ClientRowLabel } from './ClientRowLabel.tsx'
|
||||
|
||||
export const ArrayRowLabel: RowLabelComponent = () ={
|
||||
const { data } = useRowLabel<{ title: string }>()
|
||||
return (
|
||||
<div>{data.title || 'Untitled'}</div>
|
||||
)
|
||||
export const ServerRenderedArrayRowLabel = () => <ClientArrayRowLabel />
|
||||
|
||||
|
||||
// file: components/ClientArrayRowLabel.tsx
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useRowLabel } from '@payloadcms/ui'
|
||||
|
||||
export const ClientArrayRowLabel = () => {
|
||||
const { data } = useRowLabel()
|
||||
|
||||
return <div>{data?.name || 'No Name'}</div>
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ desc: Starting to build your own plugin? Find everything you need and learn best
|
||||
keywords: plugins, template, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Building your own plugin is easy, and if you're already familiar with Payload then you'll have everything you need to get started. You can either start from scratch or use the Payload plugin template to get up and running quickly.
|
||||
Building your own [Payload Plugin](./overview) is easy, and if you're already familiar with Payload then you'll have everything you need to get started. You can either start from scratch or use the [Plugin Template](#plugin-template) to get up and running quickly.
|
||||
|
||||
<Banner type="success">
|
||||
To use the template, run `npx create-payload-app@latest -t plugin -n my-new-plugin` directly in
|
||||
@@ -57,11 +57,11 @@ The initialization process goes in the following order:
|
||||
|
||||
## Plugin Template
|
||||
|
||||
In the [Payload plugin template](https://github.com/payloadcms/payload-plugin-template), you will see a common file structure that is used across plugins:
|
||||
In the [Payload Plugin Template](https://github.com/payloadcms/payload-plugin-template), you will see a common file structure that is used across plugins:
|
||||
|
||||
1. root folder - general configuration
|
||||
2. /src folder - everything related to the plugin
|
||||
3. /dev folder - sanitized test project for development
|
||||
1. `/` root folder - general configuration
|
||||
2. `/src` folder - everything related to the plugin
|
||||
3. `/dev` folder - sanitized test project for development
|
||||
|
||||
### The root folder
|
||||
|
||||
@@ -169,7 +169,7 @@ First up, the `src/index.ts` file - this is where the plugin should be imported
|
||||
|
||||
**Plugin.ts**
|
||||
|
||||
To reiterate, the essence of a payload plugin is simply to extend the Payload config - and that is exactly what we are doing in this file.
|
||||
To reiterate, the essence of a [Payload Plugin](./overview) is simply to extend the [Payload Config](../configuration/overview) - and that is exactly what we are doing in this file.
|
||||
|
||||
```
|
||||
export const samplePlugin =
|
||||
|
||||
@@ -6,16 +6,18 @@ desc: Plugins provide a great way to modularize Payload functionalities into eas
|
||||
keywords: plugins, config, configuration, extensions, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
Payload comes with a built-in Plugins infrastructure that allows developers to build their own modular and easily reusable sets of functionality.
|
||||
Payload Plugins take full advantage of the modularity of the [Payload Config](../configuration/overview), allowing developers to easily extend Payload's core functionality in a precise and granular way. This pattern allows developers to easily inject custom—sometimes complex—functionality into Payload apps from a very small touch-point.
|
||||
|
||||
There are many [Official Plugins](#official-plugins) available that solve for some of the most common uses cases, such as the [Form Builder Plugin](./seo) or [SEO Plugin](./seo). There are also [Community Plugins](#community-plugins) available, maintained entirely by contributing members. To extend Payload's functionality in some other way, you can easily [build your own plugin](./build-your-own).
|
||||
|
||||
Writing plugins is no more complex than writing regular JavaScript. If you know the basic concept of [callback functions](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) or how [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) works, and are up to speed with Payload concepts, then writing a plugin will be a breeze.
|
||||
|
||||
<Banner type="success">
|
||||
Because we rely on a simple config-based structure, Payload plugins simply take in a user's
|
||||
existing config and return a modified config with new fields, hooks, collections, admin views, or
|
||||
Because we rely on a simple config-based structure, Payload Plugins simply take in an
|
||||
existing config and returns a _modified_ config with new fields, hooks, collections, admin views, or
|
||||
anything else you can think of.
|
||||
</Banner>
|
||||
|
||||
Writing plugins is no more complex than writing regular JavaScript. If you know how [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) works and are up to speed with Payload concepts, writing a plugin will be a breeze.
|
||||
|
||||
**Example use cases:**
|
||||
|
||||
- Automatically sync data from a specific collection to HubSpot or a similar CRM when data is added or changes
|
||||
@@ -27,37 +29,49 @@ Writing plugins is no more complex than writing regular JavaScript. If you know
|
||||
- Integrate all `upload`-enabled collections with a third-party file host like S3 or Cloudinary
|
||||
- Add custom endpoints or GraphQL queries / mutations with any type of custom functionality that you can think of
|
||||
|
||||
## How to install plugins
|
||||
## Official Plugins
|
||||
|
||||
The base Payload config allows for a `plugins` property which takes an `array` of [`Plugins`](https://github.com/payloadcms/payload/blob/main/packages/payload/src/config/types.ts).
|
||||
Payload maintains a set of Official Plugins that solve for some of the common use cases. These plugins are maintained by the Payload team and its contributors and are guaranteed to be stable and up-to-date.
|
||||
|
||||
```js
|
||||
- [Form Builder](./form-builder)
|
||||
- [Nested Docs](./nested-docs)
|
||||
- [Redirects](./redirects)
|
||||
- [Search](./search)
|
||||
- [Sentry](./sentry)
|
||||
- [SEO](./seo)
|
||||
- [Stripe](./stripe)
|
||||
|
||||
You can also [build your own plugin](./build-your-own) to easily extend Payload's functionality in some other way. Once your plugin is ready, consider [sharing it with the community](#community-plugins).
|
||||
|
||||
Plugins are changing every day, so be sure to check back often to see what new plugins may have been added. If you have a specific plugin you would like to see, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions).
|
||||
|
||||
<Banner type="warning">
|
||||
For a complete list of Official Plugins, visit the [Packages Directory](https://github.com/payloadcms/payload/tree/main/packages) of the [Payload Monorepo](https://github.com/payloadcms/payload).
|
||||
</Banner>
|
||||
|
||||
## Community Plugins
|
||||
|
||||
Community Plugins are those that are maintained entirely by outside contributors. They are a great way to share your work across the ecosystem for others to use. You can discover Community Plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin).
|
||||
|
||||
Some plugins have become so widely used that they are adopted as an [Official Plugin](#official-plugin), such as the [Lexical Plugin](https://github.com/AlessioGr/payload-plugin-lexical). If you have a plugin that you think should be an Official Plugin, please feel free to start a new [Discussion](https://github.com/payloadcms/payload/discussions).
|
||||
|
||||
<Banner type="warning">
|
||||
For maintainers building plugins for others to use, please add the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin) to help others find it.
|
||||
</Banner>
|
||||
|
||||
## Installing Plugins
|
||||
|
||||
The base [Payload Config](../configuration/overview) allows for a `plugins` property which takes an `array` of [Plugin Configs](./build-your-own).
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
// note: these plugins are not real (yet?)
|
||||
import addLastModified from 'payload-add-last-modified'
|
||||
import passwordProtect from 'payload-password-protect'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
import { postgresAdapter } from '@payloadcms/db-postgres'
|
||||
|
||||
const config = buildConfig({
|
||||
collections: [
|
||||
{
|
||||
slug: 'pages',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
db: mongooseAdapter({}) // or postgresAdapter({})
|
||||
// ...
|
||||
// highlight-start
|
||||
plugins: [
|
||||
// Many plugins require options to be passed.
|
||||
// In the following example, we call the function
|
||||
@@ -72,20 +86,15 @@ const config = buildConfig({
|
||||
// To understand how to use the plugins you're interested in,
|
||||
// consult their corresponding documentation
|
||||
],
|
||||
// highlight-end
|
||||
})
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
### When Plugins are initialized
|
||||
<Banner type="warning">
|
||||
Payload Plugins are executed _after_ the incoming config is validated, but before it is sanitized and has had default options merged in. After all plugins are executed, the full config with all plugins will be sanitized.
|
||||
</Banner>
|
||||
|
||||
Payload Plugins are executed _after_ the incoming config is validated, but before it is sanitized and had default options merged in.
|
||||
|
||||
After all plugins are executed, the full config with all plugins will be sanitized.
|
||||
|
||||
## Simple example
|
||||
|
||||
Here is an example for how to automatically add a `lastModifiedBy` field to all Payload collections using a Plugin written in TypeScript.
|
||||
Here is an example what the `addLastModified` plugin from above might look like. It adds a `lastModifiedBy` field to all Payload collections. For full details, see [how to build your own plugin](./build-your-own).
|
||||
|
||||
```ts
|
||||
import { Config, Plugin } from 'payload/config'
|
||||
@@ -136,9 +145,3 @@ const addLastModified: Plugin = (incomingConfig: Config): Config => {
|
||||
|
||||
export default addLastModified
|
||||
```
|
||||
|
||||
## Available Plugins
|
||||
|
||||
You can discover existing plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin).
|
||||
|
||||
For maintainers building plugins for others to use, please add the topic to help others find it. If you would like one to be built by the core Payload team, [open a Feature Request](https://github.com/payloadcms/payload/discussions) in our GitHub Discussions board. We would be happy to review your code and maybe feature you and your plugin where appropriate.
|
||||
|
||||
@@ -16,15 +16,28 @@ to consider these main aspects:
|
||||
|
||||
1. [Basics](#basics)
|
||||
1. [Security](#security)
|
||||
1. [Your MongoDB](#mongodb)
|
||||
1. [Your database](#database)
|
||||
1. [Permanent File Storage](#file-storage)
|
||||
1. [Docker](#docker)
|
||||
|
||||
Payload can be deployed _anywhere that Next.js can run_ - including Vercel, Netlify, SST, DigitalOcean, AWS, and more. Because it's open source, you can self-host it.
|
||||
|
||||
But it's important to remember that most Payload projects will also need a database, file storage, an email provider, and a CDN. Make sure you have all of the requirements that your project needs, no matter what deployment platform you choose.
|
||||
|
||||
Often, the easiest and fastest way to deploy Payload is to use [Payload Cloud](https://payloadcms.com/new) — where you get everything you need out of the box, including:
|
||||
|
||||
1. A MongoDB Atlas database
|
||||
1. S3 file storage
|
||||
1. Resend email service
|
||||
1. Cloudflare CDN
|
||||
1. Blue / green deployments
|
||||
1. Logs
|
||||
1. And more
|
||||
|
||||
## Basics
|
||||
|
||||
In order for Payload to run, it requires both the server code and the built admin panel. These will be the `dist`
|
||||
and `build` directories by default. If you've used `create-payload-app` to create your project, executing the `build`
|
||||
npm script will build both and output these directories.
|
||||
Payload runs fully in Next.js, so the [Next.js build process](https://nextjs.org/docs/app/building-your-application/deploying) is used for building Payload. If you've used `create-payload-app` to create your project, executing the `build`
|
||||
npm script will build Payload for production.
|
||||
|
||||
## Security
|
||||
|
||||
@@ -34,9 +47,7 @@ deploying to Production, it's a good idea to double-check that you are making pr
|
||||
### The Secret Key
|
||||
|
||||
When you initialize Payload, you provide it with a `secret` property. This property should be impossible to guess and
|
||||
extremely difficult for brute-force attacks to crack. Make sure your Production `secret` is a long, complex string. It's
|
||||
often best practice to store it in an `env` file which is not checked into your Git repository, using `dotenv` to supply
|
||||
it to your `payload.init` call.
|
||||
extremely difficult for brute-force attacks to crack. Make sure your Production `secret` is a long, complex string.
|
||||
|
||||
### Double-check and thoroughly test all Access Control
|
||||
|
||||
@@ -58,34 +69,11 @@ wield that power responsibly before deploying to Production.
|
||||
to perform appropriate actions.
|
||||
</Banner>
|
||||
|
||||
### Building the Admin panel
|
||||
### Running in Production
|
||||
|
||||
Before running in Production, you need to have built a production-ready copy of the Payload Admin panel. To do this,
|
||||
Payload provides the `build` NPM script. You can use it by adding a `script` to your `package.json` file like this:
|
||||
Depending on where you deploy Payload, you may need to provide a start script to your deployment platform in order to start up Payload in production mode.
|
||||
|
||||
`package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "project-name-here",
|
||||
"scripts": {
|
||||
"build": "payload build"
|
||||
},
|
||||
"dependencies": {
|
||||
// your dependencies
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, to build Payload, you would run `npm run build` in your project folder. A production-ready Payload Admin bundle will be
|
||||
created in the `build` directory.
|
||||
|
||||
### Setting Node to Production
|
||||
|
||||
Make sure you set the environment variable `NODE_ENV` to `production`. Based on this variable, many Node packages
|
||||
automatically optimize themselves. In production, Payload automatically disables
|
||||
the [GraphQL Playground](/docs/graphql/overview#graphql-playground), serves a production-ready version of the Admin
|
||||
panel, and other changes.
|
||||
Note that this is different than running `next dev`. Generally, Next.js apps come configured with a `start` script which runs `next start`.
|
||||
|
||||
### Secure Cookie Settings
|
||||
|
||||
@@ -95,37 +83,14 @@ can [enable secure cookies](/docs/authentication/config) in your Authentication-
|
||||
### Preventing API Abuse
|
||||
|
||||
Payload comes with a robust set of built-in anti-abuse measures, such as locking out users after X amount of failed
|
||||
login attempts, request rate limiting, GraphQL query complexity limits, max `depth` settings, and
|
||||
login attempts, GraphQL query complexity limits, max `depth` settings, and
|
||||
more. [Click here to learn more](/docs/production/preventing-abuse).
|
||||
|
||||
## MongoDB
|
||||
## Database
|
||||
|
||||
Payload can be used with any MongoDB compatible database including AWS DocumentDB or Azure Cosmos DB.
|
||||
Payload can be used with any Postgres database or MongoDB-compatible database including AWS DocumentDB or Azure Cosmos DB. Make sure your production environment has access to the database that Payload uses.
|
||||
|
||||
### Managing MongoDB yourself
|
||||
|
||||
If you are using a [persistent filesystem-based cloud host](#persistent-vs-ephemeral-filesystems) such as
|
||||
a [DigitalOcean Droplet](https://www.digitalocean.com/products/droplets/) or
|
||||
an [Amazon EC2](https://aws.amazon.com/ec2/?ec2-whats-new.sort-by=item.additionalFields.postDateTime&ec2-whats-new.sort-order=desc)
|
||||
server, you might opt to install MongoDB directly on that server itself so that Node can communicate with it locally.
|
||||
With this approach, you can benefit from faster response times, but scaling can become more involved as your app's user
|
||||
base grows.
|
||||
|
||||
### Letting someone else do it
|
||||
|
||||
Alternatively, you can rely on a third-party MongoDB host such as [MongoDB Atlas](https://www.mongodb.com/). With Atlas
|
||||
or a similar cloud provider, you can trust them to take care of your database's availability, security, redundancy, and
|
||||
backups.
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
If versions are enabled and a collection has many documents you may need a minimum of an m10
|
||||
mongoDB atlas cluster if you reach a sorting `exceeded memory limit` error to view a collection
|
||||
list in the admin UI. The limitations of the m2 and m5 tier clusters are here: [Atlas M0 (Free
|
||||
Cluster), M2, and M5
|
||||
Limitations](https://www.mongodb.com/docs/atlas/reference/free-shared-limitations/?_ga=2.176267877.1329169847.1677683154-860992573.1647438381#operational-limitations).
|
||||
</Banner>
|
||||
Out of the box, Payload templates pass the `process.env.DATABASE_URI` environment variable to its database adapters, so make sure you've got that environment variable (and all others that you use) assigned in your deployment platform.
|
||||
|
||||
### DocumentDB
|
||||
|
||||
@@ -174,52 +139,21 @@ perpetually.
|
||||
with a persistent filesystem or have an integration with a third-party file host like Amazon S3.
|
||||
</Banner>
|
||||
|
||||
### Using ephemeral filesystem providers like Heroku
|
||||
### Using cloud storage providers
|
||||
|
||||
If you don't use Payload's `upload` functionality, you can go ahead and use Heroku or similar platform easily.
|
||||
Everything will work exactly as you want it to.
|
||||
If you don't use Payload's `upload` functionality, you can completely disregard this section.
|
||||
|
||||
But, if you do, and you still want to use an ephemeral filesystem provider, you can write a hook-based solution to
|
||||
_copy_ the files your users upload to a more permanent storage solution like Amazon S3 or DigitalOcean Spaces.
|
||||
But, if you do, and you still want to use an ephemeral filesystem provider, you can use one of Payload's official cloud storage plugins or write your own to save the files your users upload to a more permanent storage solution like Amazon S3 or DigitalOcean Spaces.
|
||||
|
||||
**To automatically send uploaded files to S3 or similar, you could:**
|
||||
Payload provides a list of official cloud storage adapters for you to use:
|
||||
|
||||
- Write an asynchronous `beforeChange` hook for all Collections that support Uploads, which takes any uploaded `file`
|
||||
from the `req` and sends it to an S3 bucket
|
||||
- Write an `afterRead` hook to save a `s3URL` field that automatically takes the `filename` stored and formats a full S3
|
||||
URL
|
||||
- Write an `afterDelete` hook that automatically deletes files from the S3 bucket
|
||||
- [Azure Blob Storage](https://github.com/payloadcms/payload/tree/beta/packages/storage-azure)
|
||||
- [Google Cloud Storage](https://github.com/payloadcms/payload/tree/beta/packages/storage-gcs)
|
||||
- [AWS S3](https://github.com/payloadcms/payload/tree/beta/packages/storage-s3)
|
||||
- [Uploadthing](https://github.com/payloadcms/payload/tree/beta/packages/storage-uploadthing)
|
||||
- [Vercel Blob Storage](https://github.com/payloadcms/payload/tree/beta/packages/storage-vercel-blob)
|
||||
|
||||
With the above configuration, deploying to Heroku or similar becomes no problem.
|
||||
|
||||
## DigitalOcean Tutorials
|
||||
|
||||
DigitalOcean provides extremely helpful documentation that can walk you through the entire process of creating a
|
||||
production-ready Droplet to host your Payload app:
|
||||
|
||||
1. Create a new Ubuntu 20.04 droplet on [DigitalOcean](https://digitalocean.com)
|
||||
1. [Initial server setup](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-20-04)
|
||||
1. [Install nginx](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04)
|
||||
1. [Install and secure MongoDB](https://www.digitalocean.com/community/tutorials/how-to-install-mongodb-on-ubuntu-20-04)
|
||||
1. [Create a new MongoDB and user](https://medium.com/@mhagemann/how-to-add-a-new-user-to-a-mongodb-database-d896776b5362)
|
||||
1. [Set up Node for production](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-20-04)
|
||||
|
||||
### Swap Space
|
||||
|
||||
Swap refers to a section of storage on the hard drive that is reserved to temporarily store data that can no longer fit
|
||||
within RAM. This allows for the expansion of your server's working memory, with some limitations. Swap space comes into
|
||||
play when available RAM can no longer accommodate actively used application data, enabling the system to continue
|
||||
functioning.
|
||||
|
||||
Insufficient space can lead to deployment errors and memory-related issues, resulting in application crashes, sluggish
|
||||
performance, or an unresponsive server.
|
||||
|
||||
Common deployment error due to **space limitations** (as reported by users):
|
||||
|
||||
- `Error: Command failed with exit code 1`
|
||||
|
||||
To configure swap, we recommend following this tutorial
|
||||
on [How To Add Swap Space](https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-22-04).
|
||||
Follow the docs to configure any one of these storage providers. For local development, it might be handy to simply store uploads on your own computer, and then when it comes to production, simply enable the plugin for the cloud storage vendor of your choice.
|
||||
|
||||
## Docker
|
||||
|
||||
@@ -227,31 +161,75 @@ This is an example of a multi-stage docker build of Payload for production. Ensu
|
||||
variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `DATABASE_URI` if needed.
|
||||
|
||||
```dockerfile
|
||||
FROM node:18-alpine as base
|
||||
# From https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
|
||||
|
||||
FROM base as builder
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
WORKDIR /home/node
|
||||
COPY package*.json ./
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN yarn install
|
||||
RUN yarn build
|
||||
|
||||
FROM base as runtime
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
ENV NODE_ENV=production
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn run build; \
|
||||
elif [ -f package-lock.json ]; then npm run build; \
|
||||
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
WORKDIR /home/node
|
||||
COPY package*.json ./
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
RUN yarn install --production
|
||||
COPY --from=builder /home/node/dist ./dist
|
||||
COPY --from=builder /home/node/build ./build
|
||||
ENV NODE_ENV production
|
||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "dist/server.js"]
|
||||
ENV PORT 3000
|
||||
|
||||
# server.js is created by next build from the standalone output
|
||||
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
||||
CMD HOSTNAME="0.0.0.0" node server.js
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
@@ -270,15 +248,14 @@ services:
|
||||
- .:/home/node/app
|
||||
- node_modules:/home/node/app/node_modules
|
||||
working_dir: /home/node/app/
|
||||
command: sh -c "yarn install && yarn dev"
|
||||
command: sh -c "corepack enable && corepack prepare pnpm@latest --activate && pnpm install && pnpm dev"
|
||||
depends_on:
|
||||
- mongo
|
||||
environment:
|
||||
DATABASE_URI: mongodb://mongo:27017/payload
|
||||
PORT: 3000
|
||||
NODE_ENV: development
|
||||
PAYLOAD_SECRET: TESTING
|
||||
# - postgres
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
# Ensure your DATABASE_URI uses 'mongo' as the hostname ie. mongodb://mongo/my-db-name
|
||||
mongo:
|
||||
image: mongo:latest
|
||||
ports:
|
||||
@@ -290,7 +267,17 @@ services:
|
||||
logging:
|
||||
driver: none
|
||||
|
||||
# Uncomment the following to use postgres
|
||||
# postgres:
|
||||
# restart: always
|
||||
# image: postgres:latest
|
||||
# volumes:
|
||||
# - pgdata:/var/lib/postgresql/data
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
|
||||
volumes:
|
||||
data:
|
||||
# pgdata:
|
||||
node_modules:
|
||||
```
|
||||
|
||||
@@ -14,27 +14,6 @@ Payload has built-in security best practices that can be configured to your appl
|
||||
|
||||
Set the max number of failed login attempts before a user account is locked out for a period of time. Set the `maxLoginAttempts` on the collections that feature Authentication to a reasonable but low number for your users to get in. Use the `lockTime` to set a number in milliseconds from the time a user fails their last allowed attempt that a user must wait to try again.
|
||||
|
||||
## Rate Limiting Requests
|
||||
|
||||
To prevent DDoS, brute-force, and similar attacks, you can set IP-based rate limits so that once a certain threshold of requests has been hit by a single IP, further requests from the same IP will be ignored. The Payload config `rateLimit` property accepts an object with the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`window`** | Time in milliseconds to track requests per IP. Defaults to `90000` (15 minutes). |
|
||||
| **`max`** | Number of requests served from a single IP before limiting. Defaults to `500`. |
|
||||
| **`skip`** | Function that can return true (or promise resulting in true) that will bypass limit. |
|
||||
| **`trustProxy`** | True or false, to enable to allow requests to pass through a proxy such as a load balancer or an `nginx` reverse proxy. |
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Warning:</strong>
|
||||
<br />
|
||||
Very commonly, NodeJS apps are served behind `nginx` reverse proxies and similar. If you use
|
||||
rate-limiting while you're behind a proxy, <strong>all</strong> IP addresses from everyone that
|
||||
uses your API will appear as if they are from a local origin (127.0.0.1), and your users will get
|
||||
rate-limited very quickly without cause. If you plan to host your app behind a proxy, make sure
|
||||
you set <strong>trustProxy</strong> to <strong>true</strong>.
|
||||
</Banner>
|
||||
|
||||
## Max Depth
|
||||
|
||||
Querying a collection and automatically including related documents via `depth` incurs a performance cost. Also, it's possible that your configs may have circular relationships, meaning scenarios where an infinite amount of relationships might populate back and forth until your server times out and crashes. You can prevent any potential of depth-related issues by setting a `maxDepth` property on your Payload config.. The maximum allowed depth should be as small as possible without interrupting dev experience, and it defaults to `10`.
|
||||
|
||||
@@ -7,12 +7,16 @@ keywords: uploads, images, media, overview, documentation, Content Management Sy
|
||||
---
|
||||
|
||||
<Banner>
|
||||
Payload provides for everything you need to enable file upload, storage, and management directly
|
||||
on your server—including extremely powerful file access control.
|
||||
Payload provides everything you need to enable file upload, storage, and management directly
|
||||
on your server—including extremely powerful file [access control](#access-control).
|
||||
</Banner>
|
||||
|
||||

|
||||
_Admin panel screenshot depicting a Media Collection with Upload enabled_
|
||||
<LightDarkImage
|
||||
srcLight="https://payloadcms.com/images/docs/upload-admin.jpg"
|
||||
srcDark="https://payloadcms.com/images/docs/upload-admin.jpg"
|
||||
alt="Shows an Upload enabled collection in the Payload admin panel"
|
||||
caption="Admin panel screenshot depicting a Media Collection with Upload enabled"
|
||||
/>
|
||||
|
||||
**Here are some common use cases of Uploads:**
|
||||
|
||||
@@ -23,7 +27,7 @@ _Admin panel screenshot depicting a Media Collection with Upload enabled_
|
||||
**By simply enabling Upload functionality on a Collection, Payload will automatically transform your Collection into a robust file management / storage solution. The following modifications will be made:**
|
||||
|
||||
1. `filename`, `mimeType`, and `filesize` fields will be automatically added to your Collection. Optionally, if you pass `imageSizes` to your Collection's Upload config, a [`sizes`](#image-sizes) array will also be added containing auto-resized image sizes and filenames.
|
||||
1. The Admin panel will modify its built-in `List` component to show a thumbnail gallery of your Uploads instead of the default Table view
|
||||
1. The Admin panel will modify its built-in `List` component to show a thumbnail for each upload within the List view
|
||||
1. The Admin panel will modify its `Edit` view(s) to add a new set of corresponding Upload UI which will allow for file upload
|
||||
1. The `create`, `update`, and `delete` Collection operations will be modified to support file upload, re-upload, and deletion
|
||||
|
||||
@@ -33,40 +37,17 @@ Every Payload Collection can opt-in to supporting Uploads by specifying the `upl
|
||||
|
||||
<Banner type="success">
|
||||
<strong>Tip:</strong>
|
||||
<br />A common pattern is to create a <strong>Media</strong> collection and enable <strong>
|
||||
<br />A common pattern is to create a <strong>"media"</strong> collection and enable <strong>
|
||||
upload
|
||||
</strong> on that collection.
|
||||
</Banner>
|
||||
|
||||
## Collection Upload Options
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`staticURL`** \* | The URL path to use to access your uploads. Relative path like `/media` will be served by payload. Full path like `https://example.com/media` needs to be served by another web server. |
|
||||
| **`staticDir`** \* | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. |
|
||||
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`externalFileHeaderFilter`** | Accepts existing headers and can filter/modify them. |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
|
||||
| **`handlers`** | Array of [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) handlers to execute before the built-in Payload static middleware executes. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) | |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
|
||||
_An asterisk denotes that a property above is required._
|
||||
|
||||
**Example Upload collection:**
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
staticURL: '/media',
|
||||
staticDir: 'media',
|
||||
imageSizes: [
|
||||
{
|
||||
@@ -104,9 +85,30 @@ export const Media: CollectionConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
## Payload-wide Upload Options
|
||||
### Collection Upload Options
|
||||
|
||||
Payload relies on the [`express-fileupload`](https://www.npmjs.com/package/express-fileupload) package to manage file uploads in Express. In addition to the Upload options specifiable on a Collection by Collection basis, you can also control the `express-fileupload` options by passing your base Payload config an `upload` property containing an object supportive of all `express-fileupload` properties which use `Busboy` under the hood. [Click here](https://github.com/mscdex/busboy#api) for more documentation about what you can control.
|
||||
_An asterisk denotes that an option is required._
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`adminThumbnail`** | Set the way that the Admin panel will display thumbnails for this Collection. [More](#admin-thumbnails) |
|
||||
| **`crop`** | Set to `false` to disable the cropping tool in the Admin panel. Crop is enabled by default. [More](#crop-and-focal-point-selector) |
|
||||
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
|
||||
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the Admin panel. The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
|
||||
| **`handlers`** | Array of Request handlers to execute when fetching a file, if a handler returns a Response it will be sent to the client. Otherwise Payload will retrieve and send back the file. |
|
||||
| **`imageSizes`** | If specified, image uploads will be automatically resized in accordance to these image sizes. [More](#image-sizes) |
|
||||
| **`mimeTypes`** | Restrict mimeTypes in the file picker. Array of valid mimetypes or mimetype wildcards [More](#mimetypes) |
|
||||
| **`resizeOptions`** | An object passed to the the Sharp image library to resize the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize) |
|
||||
| **`staticDir`** | The folder directory to use to store media in. Can be either an absolute path or relative to the directory that contains your config. Defaults to your collection slug |
|
||||
| **`trimOptions`** | An object passed to the the Sharp image library to trim the uploaded file. [More](https://sharp.pixelplumbing.com/api-resize#trim) |
|
||||
|
||||
|
||||
### Payload-wide Upload Options
|
||||
|
||||
Upload options are specifiable on a Collection by Collection basis, you can also control app wide options by passing your base Payload config an `upload` property containing an object supportive of all `Busboy` configuration options. [Click here](https://github.com/mscdex/busboy#api) for more documentation about what you can control.
|
||||
|
||||
A common example of what you might want to customize within Payload-wide Upload options would be to increase the allowed `fileSize` of uploads sent to Payload:
|
||||
|
||||
@@ -154,7 +156,9 @@ When an uploaded image is smaller than the defined image size, we have 3 options
|
||||
|
||||
`withoutEnlargement: undefined | false | true`
|
||||
|
||||
1.`undefined` [default]: uploading images with smaller width AND height than the image size will return null 2. `false`: always enlarge images to the image size 3. `true`: if the image is smaller than the image size, return the original image
|
||||
1. `undefined` [default]: uploading images with smaller width AND height than the image size will return null
|
||||
2. `false`: always enlarge images to the image size
|
||||
3. `true`: if the image is smaller than the image size, return the original image
|
||||
|
||||
<Banner type="error">
|
||||
<strong>Note:</strong>
|
||||
@@ -188,12 +192,9 @@ If you are using a plugin to send your files off to a third-party file storage h
|
||||
|
||||
## Admin Thumbnails
|
||||
|
||||
You can specify how Payload retrieves admin thumbnails for your upload-enabled Collections. This property accepts two different configurations:
|
||||
You can specify how Payload retrieves admin thumbnails for your upload-enabled Collections with one of the following:
|
||||
|
||||
1. A string equal to one of your provided image size names to use for the admin panel's thumbnail (seen in the example Media collection above)
|
||||
1. A function that takes the document's data and sends back a full URL to load the thumbnail. For example, to dynamically set an admin thumbnail URL, you could write a function that returns a string pointing to a different file source:
|
||||
|
||||
**Example custom Admin thumbnail:**
|
||||
1. `adminThumbnail` as a **string**, equal to one of your provided image size names.
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
@@ -201,24 +202,41 @@ import { CollectionConfig } from 'payload/types'
|
||||
export const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
staticURL: '/media',
|
||||
staticDir: 'media',
|
||||
imageSizes: [
|
||||
// ... image sizes here
|
||||
],
|
||||
// highlight-start
|
||||
adminThumbnail: ({ doc }) => `https://google.com/custom-path-to-file/${doc.filename}`,
|
||||
adminThumbnail: 'small',
|
||||
// highlight-end
|
||||
},
|
||||
imageSizes: [
|
||||
{
|
||||
name: 'small',
|
||||
fit: 'cover',
|
||||
height: 300,
|
||||
width: 900,
|
||||
},
|
||||
{
|
||||
name: 'large',
|
||||
fit: 'cover',
|
||||
height: 600,
|
||||
width: 1800,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Banner>
|
||||
<strong>Note:</strong>
|
||||
<br />
|
||||
This function runs in the browser. If your function returns `null` or `false` Payload will show
|
||||
the default generic file thumbnail instead.
|
||||
</Banner>
|
||||
2. `adminThumbnail` as a **function** that takes the document's data and sends back a full URL to load the thumbnail.
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
|
||||
export const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
// highlight-start
|
||||
adminThumbnail: ({ doc }) => `https://google.com/custom-path-to-file/${doc.filename}`,
|
||||
// highlight-end
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## MimeTypes
|
||||
|
||||
@@ -234,8 +252,6 @@ import { CollectionConfig } from 'payload/types'
|
||||
export const Media: CollectionConfig = {
|
||||
slug: 'media',
|
||||
upload: {
|
||||
staticURL: '/media',
|
||||
staticDir: 'media',
|
||||
mimeTypes: ['image/*', 'application/pdf'], // highlight-line
|
||||
},
|
||||
}
|
||||
@@ -254,7 +270,25 @@ To upload a file, use your collection's [`create`](/docs/rest-api/overview#colle
|
||||
|
||||
Send your request as a `multipart/form-data` request, using [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) if possible.
|
||||
|
||||
[Here is a walkthrough](https://muffinman.io/blog/uploading-files-using-fetch-multipart-form-data/) detailing how to upload files as `multipart/form-data` using React.
|
||||
```ts
|
||||
const fileInput = document.querySelector('#your-file-input') ;
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('file', fileInput.files[0]);
|
||||
|
||||
fetch('api/:upload-slug', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
/**
|
||||
* Do not manually add the Content-Type Header
|
||||
* the browser will handle this.
|
||||
*
|
||||
* headers: {
|
||||
* 'Content-Type': 'multipart/form-data'
|
||||
* }
|
||||
*/
|
||||
})
|
||||
```
|
||||
|
||||
## Access Control
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Collections and Globals both support the same options for configuring autosave.
|
||||
**Example config with versions, drafts, and autosave enabled:**
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
|
||||
@@ -87,7 +87,7 @@ You can use the `read` [Access Control](/docs/access-control/collections#read) m
|
||||
Here is an example that utilizes the `_status` field to require a user to be logged in to retrieve drafts:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
@@ -129,7 +129,7 @@ export const Pages: CollectionConfig = {
|
||||
Here is an example for how to write an access control function that grants access to both documents where `_status` is equal to "published" and where `_status` does not exist:
|
||||
|
||||
```ts
|
||||
import { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
|
||||
23
package.json
23
package.json
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.48",
|
||||
"version": "3.0.0-beta.55",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"bf": "pnpm run build:force",
|
||||
"build": "pnpm run build:core",
|
||||
"build:all": "turbo build",
|
||||
"build:app": "next build",
|
||||
"build:app:analyze": "cross-env ANALYZE=true next build",
|
||||
"build:clean": "pnpm clean:build",
|
||||
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\"",
|
||||
"build:core:force": "pnpm clean:build && turbo build --filter \"!@payloadcms/plugin-*\" --no-cache --force",
|
||||
"build:create-payload-app": "turbo build --filter create-payload-app",
|
||||
@@ -16,6 +18,7 @@
|
||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
||||
"build:email-resend": "turbo build --filter email-resend",
|
||||
"build:eslint-config-payload": "turbo build --filter eslint-config-payload",
|
||||
"build:force": "pnpm run build:core:force",
|
||||
"build:graphql": "turbo build --filter graphql",
|
||||
"build:live-preview": "turbo build --filter live-preview",
|
||||
"build:live-preview-react": "turbo build --filter live-preview-react",
|
||||
@@ -43,8 +46,8 @@
|
||||
"build:translations": "turbo build --filter translations",
|
||||
"build:ui": "turbo build --filter ui",
|
||||
"clean": "turbo clean",
|
||||
"clean:all": "node ./scripts/delete-recursively.js '@node_modules' 'media' '**/dist' '**/.cache' '**/.next' '**/.turbo' '**/tsconfig.tsbuildinfo' '**/payload*.tgz'",
|
||||
"clean:build": "node ./scripts/delete-recursively.js 'media' '**/dist' '**/.cache' '**/.next' '**/.turbo' '**/tsconfig.tsbuildinfo' '**/payload*.tgz'",
|
||||
"clean:all": "node ./scripts/delete-recursively.js '@node_modules' 'media' '**/dist' '**/.cache' '**/.next' '**/.turbo' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
|
||||
"clean:build": "node ./scripts/delete-recursively.js 'media' '**/dist' '**/.cache' '**/.next' '**/.turbo' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
|
||||
"clean:cache": "node ./scripts/delete-recursively.js node_modules/.cache! packages/payload/node_modules/.cache! .next",
|
||||
"dev": "cross-env NODE_OPTIONS=--no-deprecation node ./test/dev.js",
|
||||
"dev:generate-graphql-schema": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/generateGraphQLSchema.ts",
|
||||
@@ -55,6 +58,7 @@
|
||||
"docker:start": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml up -d",
|
||||
"docker:stop": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml down",
|
||||
"fix": "eslint \"packages/**/*.ts\" --fix",
|
||||
"force:build": "pnpm run build:core:force",
|
||||
"lint": "eslint \"packages/**/*.ts\"",
|
||||
"lint-staged": "lint-staged",
|
||||
"obliterate-playwright-cache-macos": "rm -rf ~/Library/Caches/ms-playwright && find /System/Volumes/Data/private/var/folders -type d -name 'playwright*' -exec rm -rf {} +",
|
||||
@@ -91,10 +95,8 @@
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/live-preview-react": "workspace:*",
|
||||
"@playwright/test": "1.43.0",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/cli": "0.3.12",
|
||||
"@swc/jest": "0.2.36",
|
||||
"@testing-library/jest-dom": "6.4.2",
|
||||
"@testing-library/react": "14.2.1",
|
||||
"@types/fs-extra": "^11.0.2",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/minimist": "1.2.5",
|
||||
@@ -137,7 +139,6 @@
|
||||
"playwright-core": "1.43.0",
|
||||
"prettier": "^3.0.3",
|
||||
"prompts": "2.4.2",
|
||||
"qs": "6.11.2",
|
||||
"react": "^19.0.0-rc-f994737d14-20240522",
|
||||
"react-dom": "^19.0.0-rc-f994737d14-20240522",
|
||||
"rimraf": "3.0.2",
|
||||
@@ -147,13 +148,12 @@
|
||||
"slash": "3.0.0",
|
||||
"sort-package-json": "^2.10.0",
|
||||
"swc-plugin-transform-remove-imports": "^1.12.1",
|
||||
"tempfile": "^3.0.0",
|
||||
"tempy": "^1.0.1",
|
||||
"ts-node": "10.9.1",
|
||||
"tsx": "^4.7.1",
|
||||
"turbo": "^1.13.3",
|
||||
"typescript": "5.4.5",
|
||||
"uuid": "^9.0.1"
|
||||
"typescript": "5.5.2",
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0 || ^19.0.0-rc-f994737d14-20240522",
|
||||
@@ -166,8 +166,7 @@
|
||||
"pnpm": {
|
||||
"allowedDeprecatedVersions": {
|
||||
"abab": "2",
|
||||
"domexception": "4",
|
||||
"uuid": "3.4.0"
|
||||
"domexception": "4"
|
||||
},
|
||||
"overrides": {
|
||||
"@types/react": "npm:types-react@19.0.0-beta.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.48",
|
||||
"version": "3.0.0-beta.55",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -40,7 +40,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm pack-template-files && pnpm typecheck && pnpm build:swc",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"pack-template-files": "tsx src/scripts/pack-template-files.ts",
|
||||
"prepublishOnly": "pnpm clean && pnpm build",
|
||||
|
||||
@@ -114,7 +114,7 @@ export async function configurePayloadConfig(args: {
|
||||
startMatch: '// storage-adapter-placeholder',
|
||||
})
|
||||
|
||||
if (replacement?.importReplacement) {
|
||||
if (replacement?.importReplacement !== undefined) {
|
||||
configLines = replaceInConfigLines({
|
||||
lines: configLines,
|
||||
replacement: [replacement.importReplacement],
|
||||
|
||||
@@ -63,6 +63,7 @@ const payloadCloudReplacement: StorageAdapterReplacement = {
|
||||
// Removes placeholders
|
||||
const diskReplacement: StorageAdapterReplacement = {
|
||||
configReplacement: [],
|
||||
importReplacement: '',
|
||||
}
|
||||
|
||||
export const storageReplacements: Record<StorageAdapterType, StorageAdapterReplacement> = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import chalk from 'chalk'
|
||||
import { Syntax, parseModule } from 'esprima-next'
|
||||
import fs from 'fs'
|
||||
|
||||
import { log , warning } from '../utils/log.js'
|
||||
import { log, warning } from '../utils/log.js'
|
||||
|
||||
export const withPayloadStatement = {
|
||||
cjs: `const { withPayload } = require('@payloadcms/next/withPayload')\n`,
|
||||
|
||||
@@ -3,7 +3,7 @@ import chalk from 'chalk'
|
||||
import path from 'path'
|
||||
import terminalLink from 'terminal-link'
|
||||
|
||||
import type { PackageManager , ProjectTemplate } from '../types.js'
|
||||
import type { PackageManager, ProjectTemplate } from '../types.js'
|
||||
|
||||
import { getValidTemplates } from '../lib/templates.js'
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emitDeclarationOnly": true,
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
"strict": true,
|
||||
"strict": true
|
||||
},
|
||||
"exclude": ["dist", "build", "tests", "test", "node_modules", ".eslintrc.js"],
|
||||
"include": ["src/**/*.ts", "src/**/*.spec.ts", "src/**/*.tsx", "src/**/*.d.ts", "src/**/*.json"]
|
||||
|
||||
@@ -14,7 +14,7 @@ npm install @payloadcms/db-mongodb
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
import { buildConfig } from 'payload'
|
||||
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
||||
|
||||
export default buildConfig({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.48",
|
||||
"version": "3.0.0-beta.55",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -14,20 +14,20 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts",
|
||||
"types": "./src/index.ts"
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/types.ts",
|
||||
"types": "./src/index.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"mock.js",
|
||||
"predefinedMigrations"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "pnpm build:swc && pnpm build:types",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc-build",
|
||||
"build": "pnpm build:types && pnpm build:swc",
|
||||
"build:swc": "swc ./src -d ./dist --config-file .swcrc-build --strip-leading-paths",
|
||||
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
||||
"clean": "rimraf {dist,*.tsbuildinfo}",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
@@ -39,7 +39,7 @@
|
||||
"mongoose": "6.12.3",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
"prompts": "2.4.2",
|
||||
"uuid": "^9.0.1"
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
@@ -55,8 +55,8 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import type { ConnectOptions } from 'mongoose'
|
||||
import type { Connect } from 'payload/database'
|
||||
import type { Connect } from 'payload'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { QueryOptions } from 'mongoose'
|
||||
import type { Count } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Count, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
import { flattenWhereToOperators } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Create } from 'payload/database'
|
||||
import type { Document, PayloadRequestWithData } from 'payload/types'
|
||||
import type { Create, Document, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CreateGlobal } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { CreateGlobal, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CreateGlobalVersion } from 'payload/database'
|
||||
import type { Document , PayloadRequestWithData } from 'payload/types'
|
||||
import type { CreateGlobalVersion, Document, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import type { CreateMigration, MigrationTemplateArgs } from 'payload/database'
|
||||
import type { CreateMigration, MigrationTemplateArgs } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { getPredefinedMigration } from 'payload/database'
|
||||
import { getPredefinedMigration } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const migrationTemplate = ({ downSQL, imports, upSQL }: MigrationTemplateArgs): string => `import {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CreateVersion } from 'payload/database'
|
||||
import type { Document , PayloadRequestWithData } from 'payload/types'
|
||||
import type { CreateVersion, Document, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { DeleteMany } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { DeleteMany, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { DeleteOne } from 'payload/database'
|
||||
import type { Document , PayloadRequestWithData } from 'payload/types'
|
||||
import type { DeleteOne, Document, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { DeleteVersions } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { DeleteVersions, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Destroy } from 'payload/database'
|
||||
import type { Destroy } from 'payload'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { Find } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { Find, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
import { flattenWhereToOperators } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { FindGlobal } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { FindGlobal, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import { combineQueries } from 'payload/database'
|
||||
import { combineQueries } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { FindGlobalVersions } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { FindGlobalVersions, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
import { buildVersionGlobalFields } from 'payload/versions'
|
||||
import { buildVersionGlobalFields, flattenWhereToOperators } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { MongooseQueryOptions } from 'mongoose'
|
||||
import type { FindOne } from 'payload/database'
|
||||
import type { Document , PayloadRequestWithData } from 'payload/types'
|
||||
import type { Document, FindOne, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { FindVersions } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { FindVersions, PayloadRequestWithData } from 'payload'
|
||||
|
||||
import { flattenWhereToOperators } from 'payload/database'
|
||||
import { flattenWhereToOperators } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { TransactionOptions } from 'mongodb'
|
||||
import type { MongoMemoryReplSet } from 'mongodb-memory-server'
|
||||
import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
|
||||
import type { Payload } from 'payload'
|
||||
import type { BaseDatabaseAdapter, DatabaseAdapterObj } from 'payload/database'
|
||||
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload } from 'payload'
|
||||
|
||||
import fs from 'fs'
|
||||
import mongoose from 'mongoose'
|
||||
import path from 'path'
|
||||
import { createDatabaseAdapter } from 'payload/database'
|
||||
import { createDatabaseAdapter } from 'payload'
|
||||
|
||||
import type { CollectionModel, GlobalModel } from './types.js'
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { Init } from 'payload/database'
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { Init, SanitizedCollectionConfig } from 'payload'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
import paginate from 'mongoose-paginate-v2'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload/versions'
|
||||
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
import type { CollectionModel } from './types.js'
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { PayloadRequestWithData } from 'payload'
|
||||
|
||||
import {
|
||||
commitTransaction,
|
||||
initTransaction,
|
||||
killTransaction,
|
||||
readMigrationFiles,
|
||||
} from 'payload/database'
|
||||
import { commitTransaction, initTransaction, killTransaction, readMigrationFiles } from 'payload'
|
||||
import prompts from 'prompts'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { PaginateOptions, Schema } from 'mongoose'
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||
import type { SanitizedCollectionConfig, SanitizedConfig } from 'payload'
|
||||
|
||||
import paginate from 'mongoose-paginate-v2'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
/* eslint-disable class-methods-use-this */
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
import type { IndexOptions, Schema, SchemaOptions, SchemaTypeOptions } from 'mongoose'
|
||||
import type { SanitizedConfig, SanitizedLocalizationConfig } from 'payload/config'
|
||||
import type {
|
||||
ArrayField,
|
||||
Block,
|
||||
@@ -23,13 +22,15 @@ import type {
|
||||
RelationshipField,
|
||||
RichTextField,
|
||||
RowField,
|
||||
SanitizedConfig,
|
||||
SanitizedLocalizationConfig,
|
||||
SelectField,
|
||||
Tab,
|
||||
TabsField,
|
||||
TextField,
|
||||
TextareaField,
|
||||
UploadField,
|
||||
} from 'payload/types'
|
||||
} from 'payload'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
import {
|
||||
@@ -37,7 +38,7 @@ import {
|
||||
fieldIsLocalized,
|
||||
fieldIsPresentationalOnly,
|
||||
tabHasName,
|
||||
} from 'payload/types'
|
||||
} from 'payload/shared'
|
||||
|
||||
export type BuildSchemaOptions = {
|
||||
allowIDField?: boolean
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Payload } from 'payload'
|
||||
import type { Field, Where } from 'payload/types'
|
||||
import type { Field, Payload, Where } from 'payload'
|
||||
|
||||
import { parseParams } from './parseParams.js'
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Payload } from 'payload'
|
||||
import type { Field, Where } from 'payload/types'
|
||||
import type { Field, Payload, Where } from 'payload'
|
||||
|
||||
import { QueryError } from 'payload/errors'
|
||||
import { QueryError } from 'payload'
|
||||
|
||||
import { parseParams } from './parseParams.js'
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { Payload } from 'payload'
|
||||
import type { PathToQuery } from 'payload/database'
|
||||
import type { Field , Operator } from 'payload/types'
|
||||
import type { Field, Operator, PathToQuery, Payload } from 'payload'
|
||||
|
||||
import ObjectIdImport from 'bson-objectid'
|
||||
import mongoose from 'mongoose'
|
||||
import { getLocalizedPaths } from 'payload/database'
|
||||
import { fieldAffectsData , validOperators } from 'payload/types'
|
||||
import { getLocalizedPaths } from 'payload'
|
||||
import { validOperators } from 'payload/shared'
|
||||
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
import type { Field } from 'payload/types'
|
||||
import type { Field, SanitizedConfig } from 'payload'
|
||||
|
||||
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SanitizedConfig, sanitizeConfig } from 'payload/config'
|
||||
import { Config } from 'payload/config'
|
||||
import { SanitizedConfig, sanitizeConfig } from 'payload'
|
||||
import { Config } from 'payload'
|
||||
import { getLocalizedSortProperty } from './getLocalizedSortProperty.js'
|
||||
|
||||
let config: SanitizedConfig
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { SanitizedConfig } from 'payload/config'
|
||||
import type { Field } from 'payload/types'
|
||||
import type { Field, SanitizedConfig } from 'payload'
|
||||
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types'
|
||||
import { flattenTopLevelFields } from 'payload/utilities'
|
||||
import { flattenTopLevelFields } from 'payload'
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/shared'
|
||||
|
||||
type Args = {
|
||||
config: SanitizedConfig
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import type { FilterQuery } from 'mongoose'
|
||||
import type { Payload } from 'payload'
|
||||
import type { Field, Operator , Where } from 'payload/types'
|
||||
import type { Field, Operator, Payload, Where } from 'payload'
|
||||
|
||||
import deepmerge from 'deepmerge'
|
||||
import { validOperators } from 'payload/types'
|
||||
import { combineMerge } from 'payload/utilities'
|
||||
import { combineMerge } from 'payload'
|
||||
import { validOperators } from 'payload/shared'
|
||||
|
||||
import { buildAndOrConditions } from './buildAndOrConditions.js'
|
||||
import { buildSearchParam } from './buildSearchParams.js'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Field, TabAsField } from 'payload/types'
|
||||
import type { Field, TabAsField } from 'payload'
|
||||
|
||||
import mongoose from 'mongoose'
|
||||
import { createArrayFromCommaDelineated } from 'payload/utilities'
|
||||
import { createArrayFromCommaDelineated } from 'payload'
|
||||
|
||||
type SanitizeQueryValueArgs = {
|
||||
field: Field | TabAsField
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { PaginateOptions } from 'mongoose'
|
||||
import type { QueryDrafts } from 'payload/database'
|
||||
import type { PayloadRequestWithData } from 'payload/types'
|
||||
import type { PayloadRequestWithData, QueryDrafts } from 'payload'
|
||||
|
||||
import { combineQueries, flattenWhereToOperators } from 'payload/database'
|
||||
import { combineQueries, flattenWhereToOperators } from 'payload'
|
||||
|
||||
import type { MongooseAdapter } from './index.js'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TransactionOptions } from 'mongodb'
|
||||
import type { BeginTransaction } from 'payload/database'
|
||||
import type { BeginTransaction } from 'payload'
|
||||
|
||||
import { APIError } from 'payload/errors'
|
||||
import { APIError } from 'payload'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import type { MongooseAdapter } from '../index.js'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user